Merge branch 'dev' of https://github.com/gogits/Gogs into issue/281

Conflicts:
	modules/base/tool.go
tokarchuk/v1.17
Justin Nuß 10 years ago
commit 91480f3791
  1. 17
      .bra.toml
  2. 1
      .gitignore
  3. 7
      .gobuild.yml
  4. 37
      .gopmfile
  5. 8
      README.md
  6. 8
      README_ZH.md
  7. 23
      bee.json
  8. 47
      cmd/serve.go
  9. 7
      cmd/update.go
  10. 163
      cmd/web.go
  11. 9
      conf/README.md
  12. 37
      conf/app.ini
  13. 23
      conf/license/BSD license
  14. 116
      conf/license/CC0 1.0 Universal
  15. 203
      conf/license/Eclipse Public License v1.0
  16. 674
      conf/license/GPL v3
  17. 13
      conf/license/ISC license
  18. 504
      conf/license/LGPL v2.1
  19. 165
      conf/license/LGPL v3
  20. 362
      conf/license/Mozilla Public License Version 2.0
  21. 179
      conf/locale/locale_en-US.ini
  22. 178
      conf/locale/locale_zh-CN.ini
  23. 8
      conf/supervisor.ini
  24. 40
      dockerfiles/README.md
  25. 0
      etc/mysql.sql
  26. 0
      etc/supervisord.conf
  27. 4
      gogs.go
  28. 64
      models/action.go
  29. 9
      models/git_diff.go
  30. 12
      models/issue.go
  31. 27
      models/login.go
  32. 92
      models/publickey.go
  33. 2
      models/release.go
  34. 155
      models/repo.go
  35. 15
      models/update.go
  36. 113
      models/user.go
  37. 6
      models/webhook.go
  38. 22
      modules/auth/admin.go
  39. 14
      modules/auth/apiv1/miscellaneous.go
  40. 175
      modules/auth/auth.go
  41. 36
      modules/auth/auth_form.go
  42. 2
      modules/auth/ldap/ldap.go
  43. 49
      modules/auth/org.go
  44. 33
      modules/auth/publickey.go
  45. 21
      modules/auth/publickey_form.go
  46. 252
      modules/auth/repo.go
  47. 165
      modules/auth/repo_form.go
  48. 299
      modules/auth/user.go
  49. 98
      modules/auth/user_form.go
  50. 4
      modules/base/base.go
  51. 28
      modules/base/template.go
  52. 162
      modules/base/tool.go
  53. 3623
      modules/bin/conf.go
  54. 201
      modules/captcha/captcha.go
  55. 487
      modules/captcha/image.go
  56. 42
      modules/captcha/image_test.go
  57. 267
      modules/captcha/siprng.go
  58. 23
      modules/captcha/siprng_test.go
  59. 26
      modules/git/blob.go
  60. 86
      modules/git/commit.go
  61. 36
      modules/git/commit_archive.go
  62. 27
      modules/git/repo.go
  63. 38
      modules/git/repo_branch.go
  64. 291
      modules/git/repo_commit.go
  65. 14
      modules/git/repo_object.go
  66. 104
      modules/git/repo_tag.go
  67. 32
      modules/git/repo_tree.go
  68. 87
      modules/git/sha1.go
  69. 40
      modules/git/signature.go
  70. 67
      modules/git/tag.go
  71. 124
      modules/git/tree.go
  72. 59
      modules/git/tree_blob.go
  73. 109
      modules/git/tree_entry.go
  74. 48
      modules/git/utils.go
  75. 43
      modules/git/version.go
  76. 73
      modules/log/console.go
  77. 237
      modules/log/file.go
  78. 251
      modules/log/log.go
  79. 22
      modules/mailer/mail.go
  80. 2
      modules/mailer/mailer.go
  81. 6
      modules/middleware/auth.go
  82. 103
      modules/middleware/binding/binding.go
  83. 263
      modules/middleware/context.go
  84. 52
      modules/middleware/logger.go
  85. 281
      modules/middleware/render.go
  86. 99
      modules/middleware/repo.go
  87. 127
      modules/middleware/static.go
  88. 2
      modules/process/manager.go
  89. 80
      modules/setting/setting.go
  90. 119
      modules/ssh/ssh.go
  91. 1
      public/css/github.min.css
  92. 7
      public/css/gogs.css
  93. BIN
      public/img/404.png
  94. BIN
      public/img/500.png
  95. BIN
      public/img/avatar_default.jpg
  96. BIN
      public/img/favicon.bak.png
  97. BIN
      public/img/favicon.png
  98. BIN
      public/img/gogs-lg.png
  99. 174
      public/js/app.js
  100. 1261
      public/ng/css/gogs.css
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,17 @@
[run]
init_cmds = [["./gogs", "web"]]
watch_all = true
watch_dirs = [
"$WORKDIR/conf/locale",
"$WORKDIR/cmd",
"$WORKDIR/models",
"$WORKDIR/modules",
"$WORKDIR/routers"
]
watch_exts = [".go", ".ini"]
build_delay = 1500
cmds = [
["go", "install"],
["go", "build"],
["./gogs", "web"]
]

1
.gitignore vendored

@ -36,3 +36,4 @@ gogs
__pycache__ __pycache__
*.pem *.pem
output* output*
config.codekit

@ -1,11 +1,12 @@
filesets: filesets:
includes: includes:
- templates - conf
- etc
- public - public
- scripts
- templates
- LICENSE - LICENSE
- README.md - README.md
- README_ZH.md - README_ZH.md
- start.bat
- start.sh
excludes: excludes:
- \.git - \.git

@ -2,24 +2,25 @@
path = github.com/gogits/gogs path = github.com/gogits/gogs
[deps] [deps]
github.com/Unknwon/cae = `commit:a1fa53b` github.com/Unknwon/cae =
github.com/Unknwon/com = `commit:019c36f` github.com/Unknwon/com =
github.com/Unknwon/goconfig = `commit:c4e325f` github.com/Unknwon/goconfig =
github.com/codegangsta/cli = `commit:bb91895` github.com/Unknwon/i18n =
github.com/go-martini/martini = `commit:49411a5` github.com/Unknwon/macaron =
github.com/go-sql-driver/mysql = `commit:b44cac6` github.com/codegangsta/cli =
github.com/go-xorm/core = `commit:267e375` github.com/go-sql-driver/mysql =
github.com/go-xorm/xorm = `commit:bd1487b` github.com/go-xorm/core =
github.com/gogits/cache = `commit:f9bb61f` github.com/go-xorm/xorm =
github.com/gogits/gfm = `commit:40f747a` github.com/gogits/cache =
github.com/gogits/git = `commit:3d9e771` github.com/gogits/gfm =
github.com/gogits/logs = `commit:0a97a46` github.com/gogits/git =
github.com/gogits/oauth2 = `commit:99cbec8` github.com/gogits/oauth2 =
github.com/gogits/session = `commit:7ab78d4` github.com/juju2013/goldap =
github.com/juju2013/goldap = `commit:f4a7f67` github.com/lib/pq =
github.com/lib/pq = `commit:529edd9` github.com/macaron-contrib/i18n =
github.com/nfnt/resize = `commit:8aee0d9` github.com/macaron-contrib/session =
github.com/nfnt/resize =
[res] [res]
include = templates|public include = conf|etc|public|scripts|templates

@ -1,11 +1,11 @@
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest)
===================== =====================
Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language. Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
![Demo](http://gowalker.org/public/gogs_demo.gif) ![Demo](http://gowalker.org/public/gogs_demo.gif)
##### Current version: 0.4.5 Alpha ##### Current version: 0.4.7 Alpha
### NOTICES ### NOTICES
@ -18,9 +18,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
## Purpose ## Purpose
Since we choose to use pure Go implementation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows with **ZERO** dependency. The goal of this project is to make the easiest, fastest and most painless way to set up a self-hosted Git service. With Go, this can be done in independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows.
More importantly, Gogs only needs one binary to setup your own project hosting on the fly!
## Overview ## Overview

@ -1,17 +1,15 @@
Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest) Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://drone.io/github.com/gogits/gogs/status.png)](https://drone.io/github.com/gogits/gogs/latest)
===================== =====================
Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。 Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。
![Demo](http://gowalker.org/public/gogs_demo.gif) ![Demo](http://gowalker.org/public/gogs_demo.gif)
##### 当前版本:0.4.5 Alpha ##### 当前版本:0.4.7 Alpha
## 开发目的 ## 开发目的
Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依赖,并且支持 Go 语言所支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。 Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。
更重要的是,您只需要一个可执行文件就能借助 Gogs 快速搭建属于您自己的代码托管服务!
## 项目概览 ## 项目概览

@ -1,23 +0,0 @@
{
"version": 0,
"gopm": {
"enable": false,
"install": false
},
"go_install": true,
"watch_ext": [],
"dir_structure": {
"watch_all": true,
"controllers": "routers",
"models": "",
"others": [
"modules",
"$GOPATH/src/github.com/gogits/logs",
"$GOPATH/src/github.com/gogits/git"
]
},
"cmd_args": [
"web"
],
"envs": []
}

@ -10,11 +10,12 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/Unknwon/com"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
@ -81,22 +82,22 @@ func runServ(k *cli.Context) {
keys := strings.Split(os.Args[2], "-") keys := strings.Split(os.Args[2], "-")
if len(keys) != 2 { if len(keys) != 2 {
println("Gogs: auth file format error") println("Gogs: auth file format error")
log.GitLogger.Fatal("Invalid auth file format: %s", os.Args[2]) log.GitLogger.Fatal(2, "Invalid auth file format: %s", os.Args[2])
} }
keyId, err := strconv.ParseInt(keys[1], 10, 64) keyId, err := com.StrTo(keys[1]).Int64()
if err != nil { if err != nil {
println("Gogs: auth file format error") println("Gogs: auth file format error")
log.GitLogger.Fatal("Invalid auth file format: %v", err) log.GitLogger.Fatal(2, "Invalid auth file format: %v", err)
} }
user, err := models.GetUserByKeyId(keyId) user, err := models.GetUserByKeyId(keyId)
if err != nil { if err != nil {
if err == models.ErrUserNotKeyOwner { if err == models.ErrUserNotKeyOwner {
println("Gogs: you are not the owner of SSH key") println("Gogs: you are not the owner of SSH key")
log.GitLogger.Fatal("Invalid owner of SSH key: %d", keyId) log.GitLogger.Fatal(2, "Invalid owner of SSH key: %d", keyId)
} }
println("Gogs: internal error:", err) println("Gogs: internal error:", err)
log.GitLogger.Fatal("Fail to get user by key ID(%d): %v", keyId, err) log.GitLogger.Fatal(2, "Fail to get user by key ID(%d): %v", keyId, err)
} }
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
@ -110,7 +111,7 @@ func runServ(k *cli.Context) {
rr := strings.SplitN(repoPath, "/", 2) rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 { if len(rr) != 2 {
println("Gogs: unavailable repository", args) println("Gogs: unavailable repository", args)
log.GitLogger.Fatal("Unavailable repository: %v", args) log.GitLogger.Fatal(2, "Unavailable repository: %v", args)
} }
repoUserName := rr[0] repoUserName := rr[0]
repoName := strings.TrimSuffix(rr[1], ".git") repoName := strings.TrimSuffix(rr[1], ".git")
@ -122,10 +123,10 @@ func runServ(k *cli.Context) {
if err != nil { if err != nil {
if err == models.ErrUserNotExist { if err == models.ErrUserNotExist {
println("Gogs: given repository owner are not registered") println("Gogs: given repository owner are not registered")
log.GitLogger.Fatal("Unregistered owner: %s", repoUserName) log.GitLogger.Fatal(2, "Unregistered owner: %s", repoUserName)
} }
println("Gogs: internal error:", err) println("Gogs: internal error:", err)
log.GitLogger.Fatal("Fail to get repository owner(%s): %v", repoUserName, err) log.GitLogger.Fatal(2, "Fail to get repository owner(%s): %v", repoUserName, err)
} }
// Access check. // Access check.
@ -134,20 +135,20 @@ func runServ(k *cli.Context) {
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE) has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE)
if err != nil { if err != nil {
println("Gogs: internal error:", err) println("Gogs: internal error:", err)
log.GitLogger.Fatal("Fail to check write access:", err) log.GitLogger.Fatal(2, "Fail to check write access:", err)
} else if !has { } else if !has {
println("You have no right to write this repository") println("You have no right to write this repository")
log.GitLogger.Fatal("User %s has no right to write repository %s", user.Name, repoPath) log.GitLogger.Fatal(2, "User %s has no right to write repository %s", user.Name, repoPath)
} }
case isRead: case isRead:
repo, err := models.GetRepositoryByName(repoUser.Id, repoName) repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
if err != nil { if err != nil {
if err == models.ErrRepoNotExist { if err == models.ErrRepoNotExist {
println("Gogs: given repository does not exist") println("Gogs: given repository does not exist")
log.GitLogger.Fatal("Repository does not exist: %s/%s", repoUser.Name, repoName) log.GitLogger.Fatal(2, "Repository does not exist: %s/%s", repoUser.Name, repoName)
} }
println("Gogs: internal error:", err) println("Gogs: internal error:", err)
log.GitLogger.Fatal("Fail to get repository: %v", err) log.GitLogger.Fatal(2, "Fail to get repository: %v", err)
} }
if !repo.IsPrivate { if !repo.IsPrivate {
@ -157,10 +158,10 @@ func runServ(k *cli.Context) {
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE) has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE)
if err != nil { if err != nil {
println("Gogs: internal error:", err) println("Gogs: internal error:", err)
log.GitLogger.Fatal("Fail to check read access:", err) log.GitLogger.Fatal(2, "Fail to check read access:", err)
} else if !has { } else if !has {
println("You have no right to access this repository") println("You have no right to access this repository")
log.GitLogger.Fatal("User %s has no right to read repository %s", user.Name, repoPath) log.GitLogger.Fatal(2, "User %s has no right to read repository %s", user.Name, repoPath)
} }
default: default:
println("Unknown command") println("Unknown command")
@ -175,29 +176,27 @@ func runServ(k *cli.Context) {
gitcmd.Stdout = os.Stdout gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin gitcmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr gitcmd.Stderr = os.Stderr
err = gitcmd.Run() if err = gitcmd.Run(); err != nil {
if err != nil { println("Gogs: internal error:", err.Error())
println("Gogs: internal error:", err) log.GitLogger.Fatal(2, "Fail to execute git command: %v", err)
log.GitLogger.Fatal("Fail to execute git command: %v", err)
} }
if isWrite { if isWrite {
tasks, err := models.GetUpdateTasksByUuid(uuid) tasks, err := models.GetUpdateTasksByUuid(uuid)
if err != nil { if err != nil {
log.GitLogger.Fatal("Fail to get update task: %v", err) log.GitLogger.Fatal(2, "Fail to get update task: %v", err)
} }
for _, task := range tasks { for _, task := range tasks {
err = models.Update(task.RefName, task.OldCommitId, task.NewCommitId, err = models.Update(task.RefName, task.OldCommitId, task.NewCommitId,
user.Name, repoUserName, repoName, user.Id) user.Name, repoUserName, repoName, user.Id)
if err != nil { if err != nil {
log.GitLogger.Fatal("Fail to update: %v", err) log.GitLogger.Fatal(2, "Fail to update: %v", err)
} }
} }
err = models.DelUpdateTasksByUuid(uuid) if err = models.DelUpdateTasksByUuid(uuid); err != nil {
if err != nil { log.GitLogger.Fatal(2, "Fail to del update task: %v", err)
log.GitLogger.Fatal("Fail to del update task: %v", err)
} }
} }
} }

@ -8,6 +8,7 @@ import (
"os" "os"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
) )
@ -30,9 +31,9 @@ func runUpdate(c *cli.Context) {
args := c.Args() args := c.Args()
if len(args) != 3 { if len(args) != 3 {
log.GitLogger.Fatal("received less 3 parameters") log.GitLogger.Fatal(2, "received less 3 parameters")
} else if args[0] == "" { } else if args[0] == "" {
log.GitLogger.Fatal("refName is empty, shouldn't use") log.GitLogger.Fatal(2, "refName is empty, shouldn't use")
} }
uuid := os.Getenv("uuid") uuid := os.Getenv("uuid")
@ -45,6 +46,6 @@ func runUpdate(c *cli.Context) {
} }
if err := models.AddUpdateTask(&task); err != nil { if err := models.AddUpdateTask(&task); err != nil {
log.GitLogger.Fatal(err.Error()) log.GitLogger.Fatal(2, err.Error())
} }
} }

@ -12,13 +12,16 @@ import (
"os" "os"
"path" "path"
"github.com/Unknwon/macaron"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/go-martini/martini" "github.com/macaron-contrib/i18n"
"github.com/macaron-contrib/session"
"github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/auth/apiv1" "github.com/gogits/gogs/modules/auth/apiv1"
"github.com/gogits/gogs/modules/avatar" "github.com/gogits/gogs/modules/avatar"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/captcha"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware" "github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/middleware/binding" "github.com/gogits/gogs/modules/middleware/binding"
@ -43,45 +46,55 @@ and it takes care of all the other things for you`,
// checkVersion checks if binary matches the version of temolate files. // checkVersion checks if binary matches the version of temolate files.
func checkVersion() { func checkVersion() {
data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/VERSION")) data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/.VERSION"))
if err != nil { if err != nil {
log.Fatal("Fail to read 'templates/VERSION': %v", err) log.Fatal(4, "Fail to read 'templates/.VERSION': %v", err)
} }
if string(data) != setting.AppVer { if string(data) != setting.AppVer {
log.Fatal("Binary and template file version does not match, did you forget to recompile?") log.Fatal(4, "Binary and template file version does not match, did you forget to recompile?")
} }
} }
func newMartini() *martini.ClassicMartini { // newMacaron initializes Macaron instance.
r := martini.NewRouter() func newMacaron() *macaron.Macaron {
m := martini.New() m := macaron.New()
m.Use(middleware.Logger()) m.Use(macaron.Logger())
m.Use(martini.Recovery()) m.Use(macaron.Recovery())
m.Use(middleware.Static("public", if setting.EnableGzip {
middleware.StaticOptions{SkipLogging: !setting.DisableRouterLog})) m.Use(macaron.Gzip())
m.MapTo(r, (*martini.Routes)(nil)) }
m.Action(r.Handle) m.Use(macaron.Static("public",
return &martini.ClassicMartini{m, r} macaron.StaticOptions{
SkipLogging: !setting.DisableRouterLog,
},
))
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "templates"),
Funcs: []template.FuncMap{base.TemplateFuncs},
IndentJSON: macaron.Env != macaron.PROD,
}))
m.Use(i18n.I18n(i18n.LocaleOptions{
Langs: setting.Langs,
Names: setting.Names,
Redirect: true,
}))
m.Use(session.Sessioner(session.Options{
Provider: setting.SessionProvider,
Config: *setting.SessionConfig,
}))
m.Use(middleware.Contexter())
return m
} }
func runWeb(*cli.Context) { func runWeb(*cli.Context) {
routers.GlobalInit() routers.GlobalInit()
checkVersion() checkVersion()
m := newMartini() m := newMacaron()
// Middlewares.
m.Use(middleware.Renderer(middleware.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "templates"),
Funcs: []template.FuncMap{base.TemplateFuncs},
IndentJSON: true,
}))
m.Use(middleware.InitContext())
reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true}) reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true})
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: setting.Service.RequireSignInView}) ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: setting.Service.RequireSignInView})
ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true}) ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
bindIgnErr := binding.BindIgnErr bindIgnErr := binding.BindIgnErr
@ -90,14 +103,15 @@ func runWeb(*cli.Context) {
m.Get("/", ignSignIn, routers.Home) m.Get("/", ignSignIn, routers.Home)
m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install) m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost) m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
m.Group("", func(r martini.Router) { m.Group("", func(r *macaron.Router) {
r.Get("/issues", user.Issues) r.Get("/issues", user.Issues)
r.Get("/pulls", user.Pulls) r.Get("/pulls", user.Pulls)
r.Get("/stars", user.Stars) r.Get("/stars", user.Stars)
}, reqSignIn) }, reqSignIn)
m.Group("/api", func(_ martini.Router) { // API routers.
m.Group("/v1", func(r martini.Router) { m.Group("/api", func(_ *macaron.Router) {
m.Group("/v1", func(r *macaron.Router) {
// Miscellaneous. // Miscellaneous.
r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown) r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
r.Post("/markdown/raw", v1.MarkdownRaw) r.Post("/markdown/raw", v1.MarkdownRaw)
@ -118,41 +132,46 @@ func runWeb(*cli.Context) {
os.MkdirAll("public/img/avatar/", os.ModePerm) os.MkdirAll("public/img/avatar/", os.ModePerm)
m.Get("/avatar/:hash", avt.ServeHTTP) m.Get("/avatar/:hash", avt.ServeHTTP)
m.Group("/user", func(r martini.Router) { // User routers.
m.Group("/user", func(r *macaron.Router) {
r.Get("/login", user.SignIn) r.Get("/login", user.SignIn)
r.Post("/login", bindIgnErr(auth.LogInForm{}), user.SignInPost) r.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
r.Get("/login/:name", user.SocialSignIn) r.Get("/login/:name", user.SocialSignIn)
r.Get("/sign_up", user.SignUp) r.Get("/sign_up", user.SignUp)
r.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost) r.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
r.Get("/reset_password", user.ResetPasswd) r.Get("/reset_password", user.ResetPasswd)
r.Post("/reset_password", user.ResetPasswdPost) r.Post("/reset_password", user.ResetPasswdPost)
}, reqSignOut) }, reqSignOut)
m.Group("/user", func(r martini.Router) { m.Group("/user", func(r *macaron.Router) {
r.Get("/delete", user.Delete) r.Get("/settings", user.Settings)
r.Post("/delete", user.DeletePost) r.Post("/settings", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
r.Get("/settings", user.Setting) m.Group("/settings", func(r *macaron.Router) {
r.Post("/settings", bindIgnErr(auth.UpdateProfileForm{}), user.SettingPost) r.Get("/password", user.SettingsPassword)
r.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
r.Get("/ssh", user.SettingsSSHKeys)
r.Post("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
r.Get("/social", user.SettingsSocial)
r.Get("/orgs", user.SettingsOrgs)
r.Route("/delete", "GET,POST", user.SettingsDelete)
})
}, reqSignIn) }, reqSignIn)
m.Group("/user", func(r martini.Router) { m.Group("/user", func(r *macaron.Router) {
r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
r.Any("/activate", user.Activate) r.Any("/activate", user.Activate)
r.Get("/email2user", user.Email2User) r.Get("/email2user", user.Email2User)
r.Get("/forget_password", user.ForgotPasswd) r.Get("/forget_password", user.ForgotPasswd)
r.Post("/forget_password", user.ForgotPasswdPost) r.Post("/forget_password", user.ForgotPasswdPost)
r.Get("/logout", user.SignOut) r.Get("/logout", user.SignOut)
}) })
m.Group("/user/settings", func(r martini.Router) {
r.Get("/social", user.SettingSocial)
r.Get("/password", user.SettingPassword)
r.Post("/password", bindIgnErr(auth.UpdatePasswdForm{}), user.SettingPasswordPost)
r.Any("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys)
r.Get("/notification", user.SettingNotification)
r.Get("/security", user.SettingSecurity)
}, reqSignIn)
m.Get("/user/:username", ignSignIn, user.Profile) m.Get("/user/:username", ignSignIn, user.Profile) // TODO: Legacy
// Captcha service.
cpt := captcha.NewCaptcha("/captcha/", setting.Cache)
m.Map(cpt)
m.Get("/captcha/*", cpt.Handler)
m.Group("/repo", func(r martini.Router) { m.Group("/repo", func(r *macaron.Router) {
r.Get("/create", repo.Create) r.Get("/create", repo.Create)
r.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost) r.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
r.Get("/migrate", repo.Migrate) r.Get("/migrate", repo.Migrate)
@ -162,14 +181,14 @@ func runWeb(*cli.Context) {
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true}) adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
m.Get("/admin", adminReq, admin.Dashboard) m.Get("/admin", adminReq, admin.Dashboard)
m.Group("/admin", func(r martini.Router) { m.Group("/admin", func(r *macaron.Router) {
r.Get("/users", admin.Users) r.Get("/users", admin.Users)
r.Get("/repos", admin.Repositories) r.Get("/repos", admin.Repositories)
r.Get("/auths", admin.Auths) r.Get("/auths", admin.Auths)
r.Get("/config", admin.Config) r.Get("/config", admin.Config)
r.Get("/monitor", admin.Monitor) r.Get("/monitor", admin.Monitor)
}, adminReq) }, adminReq)
m.Group("/admin/users", func(r martini.Router) { m.Group("/admin/users", func(r *macaron.Router) {
r.Get("/new", admin.NewUser) r.Get("/new", admin.NewUser)
r.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost) r.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
r.Get("/:userid", admin.EditUser) r.Get("/:userid", admin.EditUser)
@ -177,7 +196,7 @@ func runWeb(*cli.Context) {
r.Get("/:userid/delete", admin.DeleteUser) r.Get("/:userid/delete", admin.DeleteUser)
}, adminReq) }, adminReq)
m.Group("/admin/auths", func(r martini.Router) { m.Group("/admin/auths", func(r *macaron.Router) {
r.Get("/new", admin.NewAuthSource) r.Get("/new", admin.NewAuthSource)
r.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost) r.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
r.Get("/:authid", admin.EditAuthSource) r.Get("/:authid", admin.EditAuthSource)
@ -187,14 +206,15 @@ func runWeb(*cli.Context) {
m.Get("/:username", ignSignIn, user.Profile) m.Get("/:username", ignSignIn, user.Profile)
if martini.Env == martini.Dev { if macaron.Env == macaron.DEV {
m.Get("/template/**", dev.TemplatePreview) m.Get("/template/**", dev.TemplatePreview)
dev.RegisterDebugRoutes(m) dev.RegisterDebugRoutes(m)
} }
reqTrueOwner := middleware.RequireTrueOwner() reqTrueOwner := middleware.RequireTrueOwner()
m.Group("/org", func(r martini.Router) { // Organization routers.
m.Group("/org", func(r *macaron.Router) {
r.Get("/create", org.New) r.Get("/create", org.New)
r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.NewPost) r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.NewPost)
r.Get("/:org", org.Home) r.Get("/:org", org.Home)
@ -213,11 +233,11 @@ func runWeb(*cli.Context) {
r.Post("/:org/settings/delete", org.DeletePost) r.Post("/:org/settings/delete", org.DeletePost)
}, reqSignIn) }, reqSignIn)
m.Group("/:username/:reponame", func(r martini.Router) { m.Group("/:username/:reponame", func(r *macaron.Router) {
r.Get("/settings", repo.Setting) r.Get("/settings", repo.Setting)
r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost) r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
m.Group("/settings", func(r martini.Router) { m.Group("/settings", func(r *macaron.Router) {
r.Get("/collaboration", repo.Collaboration) r.Get("/collaboration", repo.Collaboration)
r.Post("/collaboration", repo.CollaborationPost) r.Post("/collaboration", repo.CollaborationPost)
r.Get("/hooks", repo.WebHooks) r.Get("/hooks", repo.WebHooks)
@ -228,10 +248,10 @@ func runWeb(*cli.Context) {
}) })
}, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner)
m.Group("/:username/:reponame", func(r martini.Router) { m.Group("/:username/:reponame", func(r *macaron.Router) {
r.Get("/action/:action", repo.Action) // r.Get("/action/:action", repo.Action)
m.Group("/issues", func(r martini.Router) { m.Group("/issues", func(r *macaron.Router) {
r.Get("/new", repo.CreateIssue) r.Get("/new", repo.CreateIssue)
r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost) r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
@ -255,35 +275,36 @@ func runWeb(*cli.Context) {
r.Get("/releases/edit/:tagname", repo.EditRelease) r.Get("/releases/edit/:tagname", repo.EditRelease)
}, reqSignIn, middleware.RepoAssignment(true)) }, reqSignIn, middleware.RepoAssignment(true))
m.Group("/:username/:reponame", func(r martini.Router) { m.Group("/:username/:reponame", func(r *macaron.Router) {
r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
}, reqSignIn, middleware.RepoAssignment(true, true)) }, reqSignIn, middleware.RepoAssignment(true, true))
m.Group("/:username/:reponame", func(r martini.Router) { m.Group("/:username/:reponame", func(r *macaron.Router) {
r.Get("/issues", repo.Issues) r.Get("/issues", repo.Issues)
r.Get("/issues/:index", repo.ViewIssue) r.Get("/issues/:index", repo.ViewIssue)
r.Get("/pulls", repo.Pulls) r.Get("/pulls", repo.Pulls)
r.Get("/branches", repo.Branches) r.Get("/branches", repo.Branches)
}, ignSignIn, middleware.RepoAssignment(true)) }, ignSignIn, middleware.RepoAssignment(true))
m.Group("/:username/:reponame", func(r martini.Router) { m.Group("/:username/:reponame", func(r *macaron.Router) {
r.Get("/src/:branchname", repo.Single) r.Get("/src/:branchname", repo.Home)
r.Get("/src/:branchname/**", repo.Single) r.Get("/src/:branchname/*", repo.Home)
r.Get("/raw/:branchname/**", repo.SingleDownload) r.Get("/raw/:branchname/*", repo.SingleDownload)
r.Get("/commits/:branchname", repo.Commits) r.Get("/commits/:branchname", repo.Commits)
r.Get("/commits/:branchname/search", repo.SearchCommits) r.Get("/commits/:branchname/search", repo.SearchCommits)
r.Get("/commits/:branchname/**", repo.FileHistory) r.Get("/commits/:branchname/*", repo.FileHistory)
r.Get("/commit/:branchname", repo.Diff) r.Get("/commit/:branchname", repo.Diff)
r.Get("/commit/:branchname/**", repo.Diff) r.Get("/commit/:branchname/*", repo.Diff)
r.Get("/releases", repo.Releases) r.Get("/releases", repo.Releases)
r.Get("/archive/:branchname/:reponame.zip", repo.ZipDownload) r.Get("/archive/*.*", repo.Download)
r.Get("/archive/:branchname/:reponame.tar.gz", repo.TarGzDownload)
}, ignSignIn, middleware.RepoAssignment(true, true)) }, ignSignIn, middleware.RepoAssignment(true, true))
m.Group("/:username", func(r martini.Router) { m.Group("/:username", func(r *macaron.Router) {
r.Get("/:reponame", middleware.RepoAssignment(true, true, true), repo.Single) r.Get("/:reponame", middleware.RepoAssignment(true, true, true), repo.Home)
r.Any("/:reponame/**", repo.Http) m.Group("/:reponame", func(r *macaron.Router) {
r.Any("/*", repo.Http)
})
}, ignSignInAndCsrf) }, ignSignInAndCsrf)
// Not found handler. // Not found handler.
@ -298,10 +319,10 @@ func runWeb(*cli.Context) {
case setting.HTTPS: case setting.HTTPS:
err = http.ListenAndServeTLS(listenAddr, setting.CertFile, setting.KeyFile, m) err = http.ListenAndServeTLS(listenAddr, setting.CertFile, setting.KeyFile, m)
default: default:
log.Fatal("Invalid protocol: %s", setting.Protocol) log.Fatal(4, "Invalid protocol: %s", setting.Protocol)
} }
if err != nil { if err != nil {
log.Fatal("Fail to start server: %v", err) log.Fatal(4, "Fail to start server: %v", err)
} }
} }

@ -1,9 +0,0 @@
## NOTICE
This directory only used for development, and us [go-bindata](https://github.com/jteeuwen/go-bindata) to store in memory for releases.
To apply any change in this directory, install [go-bindata](https://github.com/jteeuwen/go-bindata), and then execute following command in root of source directory:
```
$ go-bindata -ignore="\\.DS_Store|README.md" -o modules/bin/conf.go -pkg="bin" conf/...
```

@ -28,6 +28,8 @@ KEY_FILE = custom/https/key.pem
; Upper level of template and static file path ; Upper level of template and static file path
; default is the path where Gogs is executed ; default is the path where Gogs is executed
STATIC_ROOT_PATH = STATIC_ROOT_PATH =
; Application level GZIP support
ENABLE_GZIP = false
[database] [database]
; Either "mysql", "postgres" or "sqlite3", it's your choice ; Either "mysql", "postgres" or "sqlite3", it's your choice
@ -125,14 +127,6 @@ SCOPES = all
AUTH_URL = https://open.t.qq.com/cgi-bin/oauth2/authorize AUTH_URL = https://open.t.qq.com/cgi-bin/oauth2/authorize
TOKEN_URL = https://open.t.qq.com/cgi-bin/oauth2/access_token TOKEN_URL = https://open.t.qq.com/cgi-bin/oauth2/access_token
[oauth.twitter]
ENABLED = false
CLIENT_ID =
CLIENT_SECRET =
SCOPES = all
AUTH_URL = https://api.twitter.com/oauth/authorize
TOKEN_URL = https://api.twitter.com/oauth/access_token
[oauth.weibo] [oauth.weibo]
ENABLED = false ENABLED = false
CLIENT_ID = CLIENT_ID =
@ -147,8 +141,8 @@ ADAPTER = memory
; For "memory" only, GC interval in seconds, default is 60 ; For "memory" only, GC interval in seconds, default is 60
INTERVAL = 60 INTERVAL = 60
; For "redis" and "memcache", connection host address ; For "redis" and "memcache", connection host address
; redis: ":6039" ; redis: `:6039`
; memcache: "127.0.0.1:11211" ; memcache: `127.0.0.1:11211`
HOST = HOST =
[session] [session]
@ -156,9 +150,9 @@ HOST =
PROVIDER = file PROVIDER = file
; Provider config options ; Provider config options
; memory: not have any config yet ; memory: not have any config yet
; file: session file path, e.g. "data/sessions" ; file: session file path, e.g. `data/sessions`
; redis: config like redis server addr, poolSize, password, e.g. "127.0.0.1:6379,100,astaxie" ; redis: config like redis server addr, poolSize, password, e.g. `127.0.0.1:6379,100,gogs`
; mysql: go-sql-driver/mysql dsn config string, e.g. "root:password@/session_table" ; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
PROVIDER_CONFIG = data/sessions PROVIDER_CONFIG = data/sessions
; Session cookie name ; Session cookie name
COOKIE_NAME = i_like_gogits COOKIE_NAME = i_like_gogits
@ -182,15 +176,15 @@ DISABLE_GRAVATAR = false
[attachment] [attachment]
; Whether attachments are enabled. Defaults to `true` ; Whether attachments are enabled. Defaults to `true`
ENABLE = ENABLE = true
; Path for attachments. Defaults to files/attachments ; Path for attachments. Defaults to `data/attachments`
PATH = PATH = data/attachments
; One or more allowed types, e.g. image/jpeg|image/png ; One or more allowed types, e.g. image/jpeg|image/png
ALLOWED_TYPES = ALLOWED_TYPES = image/jpeg|image/png
; Max size of each file. Defaults to 32MB ; Max size of each file. Defaults to 32MB
MAX_SIZE MAX_SIZE = 32
; Max number of files per upload. Defaults to 10 ; Max number of files per upload. Defaults to 10
MAX_FILES = MAX_FILES = 10
[time] [time]
; Specifies the format for fully outputed dates. Defaults to RFC1123 ; Specifies the format for fully outputed dates. Defaults to RFC1123
@ -215,7 +209,6 @@ LEVEL =
; For "file" mode only ; For "file" mode only
[log.file] [log.file]
LEVEL = LEVEL =
FILE_NAME = log/gogs.log
; This enables automated log rotate(switch of following options), default is true ; This enables automated log rotate(switch of following options), default is true
LOG_ROTATE = true LOG_ROTATE = true
; Max line number of single file, default is 1000000 ; Max line number of single file, default is 1000000
@ -259,3 +252,7 @@ LEVEL =
DRIVER = DRIVER =
; Based on xorm, e.g.: root:root@localhost/gogs?charset=utf8 ; Based on xorm, e.g.: root:root@localhost/gogs?charset=utf8
CONN = CONN =
[i18n]
LANGS = en-US,zh-CN
NAMES = English,简体中文

@ -0,0 +1,23 @@
Copyright (c) 2014
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,116 @@
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

@ -0,0 +1,203 @@
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation
distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates'
from a Contributor if it was added to the Program by such Contributor
itself or anyone acting on such Contributor's behalf. Contributions do not
include additions to the Program which: (i) are separate modules of
software distributed in conjunction with the Program under their own
license agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly
perform, distribute and sublicense the Contribution of such Contributor,
if any, and such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of
the Contribution and the Program if, at the time the Contribution is
added by the Contributor, such addition of the Contribution causes such
combination to be covered by the Licensed Patents. The patent license
shall not apply to any other combinations which include the Contribution.
No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses
to its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor
disclaims any liability to Recipient for claims brought by any other
entity based on infringement of intellectual property rights or
otherwise. As a condition to exercising the rights and licenses granted
hereunder, each Recipient hereby assumes sole responsibility to secure
any other intellectual property rights needed, if any. For example, if a
third party patent license is required to allow Recipient to distribute
the Program, it is Recipient's responsibility to acquire that license
before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under
its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties
and conditions, express and implied, including warranties or
conditions of title and non-infringement, and implied warranties or
conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are
offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained
within the Program.
Each Contributor must identify itself as the originator of its Contribution,
if
any, in a manner that reasonably allows subsequent Recipients to identify the
originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a manner
which does not create potential liability for other Contributors. Therefore,
if a Contributor includes the Program in a commercial product offering, such
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
every other Contributor ("Indemnified Contributor") against any losses,
damages and costs (collectively "Losses") arising from claims, lawsuits and
other legal actions brought by a third party against the Indemnified
Contributor to the extent caused by the acts or omissions of such Commercial
Contributor in connection with its distribution of the Program in a commercial
product offering. The obligations in this section do not apply to any claims
or Losses relating to any actual or alleged intellectual property
infringement. In order to qualify, an Indemnified Contributor must:
a) promptly notify the Commercial Contributor in writing of such claim, and
b) allow the Commercial Contributor to control, and cooperate with the
Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such claim at
its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If
that Commercial Contributor then makes performance claims, or offers
warranties related to Product X, those performance claims and warranties are
such Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a
court requires any other Contributor to pay any damages as a result, the
Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
Recipient is solely responsible for determining the appropriateness of using
and distributing the Program and assumes all risks associated with its
exercise of rights under this Agreement , including but not limited to the
risks and costs of program errors, compliance with applicable laws, damage to
or loss of data, programs or equipment, and unavailability or interruption of
operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of the
remainder of the terms of this Agreement, and without further action by the
parties hereto, such provision shall be reformed to the minimum extent
necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted
under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and does
not cure such failure in a reasonable period of time after becoming aware of
such noncompliance. If all Recipient's rights under this Agreement terminate,
Recipient agrees to cease use and distribution of the Program as soon as
reasonably practicable. However, Recipient's obligations under this Agreement
and any licenses granted by Recipient relating to the Program shall continue
and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to
time. No one other than the Agreement Steward has the right to modify this
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
Eclipse Foundation may assign the responsibility to serve as the Agreement
Steward to a suitable separate entity. Each new version of the Agreement will
be given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version of the
Agreement is published, Contributor may elect to distribute the Program
(including its Contributions) under the new version. Except as expressly
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
licenses to the intellectual property of any Contributor under this Agreement,
whether expressly, by implication, estoppel or otherwise. All rights in the
Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial in
any resulting litigation.

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

@ -0,0 +1,13 @@
Copyright (c) 2014
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

@ -0,0 +1,504 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
(This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.)
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random
Hacker.
{signature of Ty Coon}, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

@ -0,0 +1,179 @@
app_desc = A painless self-hosted Git service written in Go
home = Home
dashboard = Dashboard
explore = Explore
help = Help
sign_in = Sign In
sign_out = Sign Out
sign_up = Sign Up
register = Register
website = Website
version = Version
page = Page
template = Template
language = Language
username = Username
email = E-mail
password = Password
re_type = Re-Type
captcha = Captcha
repository = Repository
organization = Organization
mirror = Mirror
new_repo = New Repository
new_migrate = New Migration
new_org = New Organization
manage_org = Manage Organizations
admin_panel = Admin Panel
account_settings = Account Settings
settings = Settings
news_feed = News Feed
pull_requests = Pull Requests
issues = Issues
cancel = Cancel
[home]
uname_holder = Username or E-mail
password_holder = Password
switch_dashboard_context = Switch Dashboard Context
my_repos = My Repositories
collaborative_repos = Collaborative Repositories
my_orgs = My Organizations
my_mirrors = My Mirrors
[auth]
create_new_account = Create New Account
register_hepler_msg = Already have an account? Sign in now!
disable_register_prompt = Sorry, registration has been disabled. Please contact the site administrator.
remember_me = Remember Me
forget_password = Fotget password?
sign_up_now = Need an account? Sign up now.
[form]
UserName = Username
Email = E-mail address
Password = Password
Retype = Re-type password
SSHTitle = SSH key name
require_error = ` cannot be empty.`
alpha_dash_error = ` must be valid alpha or numeric or dash(-_) characters.`
alpha_dash_dot_error = ` must be valid alpha or numeric or dash(-_) or dot characters.`
min_size_error = ` must contain at least %s characters.`
max_size_error = ` must contain at most %s characters.`
email_error = ` is not a valid e-mail address.`
url_error = ` is not a valid URL.`
unknown_error = Unknown error:
captcha_incorrect = Captcha didn't match.
password_not_match = Password and re-type password are not same.
username_been_taken = Username has been already taken.
repo_name_been_taken = Repository name has been already taken.
email_been_used = E-mail address has been already used.
ssh_key_been_used = Public key name has been used.
illegal_username = Your username contains illegal characters.
illegal_repo_name = Repository name contains illegal characters.
username_password_incorrect = Username or password is not correct.
invalid_ssh_key = Sorry, we're not able to verify your SSH key: %s
still_own_repo = Your account still have ownership of repository, you have to delete or transfer them first.
[settings]
profile = Profile
password = Password
ssh_keys = SSH Keys
social = Social Accounts
orgs = Organizations
delete = Delete Accoount
public_profile = Public Profile
profile_desc = Your Email address is public and will be used for any account related notifications, and any web based operations made via the site.
full_name = Full Name
website = Website
location = Location
update_profile = Update Profile
update_profile_success = Your profile has been successfully updated.
change_password = Change Password
old_password = Current Password
new_password = New Password
password_incorrect = Current password is not correct.
change_password_success = Password is changed successfully. You can now sign in via new password.
manage_ssh_keys = Manage SSH Keys
add_key = Add Key
ssh_desc = This is a list of SSH keys associated with your account. Remove any keys that you do not recognize.
ssh_helper = <strong>Need help?</strong> Check out our guide to <a href="https://help.github.com/articles/generating-ssh-keys">generating SSH keys</a> or troubleshoot <a href="https://help.github.com/ssh-issues/">common SSH Problems</a>.
add_new_key = Add SSH Key
key_name = Key Name
key_content = Content
add_key_success = New SSH Key has been added!
delete_key = Delete
add_on = Added on
last_used = Last used on
no_activity = No recent activity
manage_orgs = Manage Organizations
manage_social = Manage Associated Social Accounts
delete_account = Delete Your Account
delete_prompt = The operation will delete your account permanently, and <strong>CANNOT</strong> be undo!
confirm_delete_account = Confirm Deletion
[repo]
owner = Owner
repo_name = Repository Name
repo_name_helper = Great repository names are short, memorable and <strong>unique</strong>.
visibility = Visibility
visiblity_helper = This repository is <span class="label label-red label-radius">Private</span>
repo_desc = Description
repo_lang = Language
repo_lang_helper = Select a .gitignore file
license = License
license_helper = Select a license file
init_readme = Initialize this repository with a README.md
create_repo = Create Repository
[action]
create_repo = created repository <a href="/%s">%s</a>
commit_repo = pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>
create_issue = opened issue <a href="/%s/issues/%s">%s#%s</a>
comment_issue = commented on issue <a href="/%s/issues/%s">%s#%s</a>
[tool]
ago = ago
from_now = from now
now = now
1s = 1 second %s
1m = 1 mintue %s
1h = 1 hour %s
1d = 1 day %s
1w = 1 week %s
1mon = 1 month %s
1y = 1 year %s
seconds = %d seconds %s
minutes = %d minutes %s
hours = %d hours %s
days = %d days %s
weeks = %d weeks %s
months = %d months %s
years = %d years %s

@ -0,0 +1,178 @@
app_desc = 基于 Go 语言的自助 Git 服务
home = 首页
dashboard = 控制面板
explore = 探索
help = 帮助
sign_in = 登录
sign_out = 退出
sign_up = 注册
register = 注册
website = 官方网站
version = 当前版本
page = 页面
template = 模板
language = 语言选项
username = 用户名
email = 邮箱
password = 密码
re_type = 确认密码
captcha = 验证码
repository = 仓库
organization = 组织
mirror = 镜像
new_repo = 创建新的仓库
new_migrate = 迁移外部仓库
new_org = 创建新的组织
manage_org = 管理我的组织
admin_panel = 管理面板
account_settings = 帐户设置
settings = 帐户设置
news_feed = 最新活动
pull_requests = 合并请求
issues = 工单管理
cancel = 取消
[home]
uname_holder = 用户名或邮箱
password_holder = 密码
switch_dashboard_context = 切换控制面板用户
my_repos = 我的仓库
collaborative_repos = 参与协作的仓库
my_orgs = 我的组织
my_mirrors = 我的镜像
[auth]
create_new_account = 创建帐户
register_hepler_msg = 已经注册?立即登录!
disable_register_prompt = 对不起,注册功能已被关闭。请联系网站管理员。
remember_me = 记住登录
forget_password = 忘记密码?
sign_up_now = 还没帐户?马上注册。
[form]
UserName = 用户名
Email = 邮箱地址
Password = 密码
Retype = 确认密码
SSHTitle = SSH 密钥名称
require_error = 不能为空。
alpha_dash_error = 必须为英文字母、阿拉伯数字或横线(-_)。
alpha_dash_dot_error = 必须为英文字母、阿拉伯数字、横线(-_)或点。
min_size_error = 长度最小为 %s 个字符。
max_size_error = 长度最大为 %s 个字符。
email_error = 不是一个有效的邮箱地址。
url_error = 不是一个有效的 URL。
unknown_error = 未知错误:
captcha_incorrect = 验证码未匹配。
password_not_match = 密码与确认密码未匹配。
username_been_taken = 用户名已经被占用。
repo_name_been_taken = 仓库名称已经被占用。
email_been_used = 邮箱地址已经被使用。
ssh_key_been_used = SSH 密钥已经被使用。
illegal_username = 您的用户名包含非法字符。
illegal_repo_name = 仓库名称包含非法字符。
username_password_incorrect = 用户名或密码不正确。
invalid_ssh_key = 很抱歉,我们无法验证您输入的 SSH 密钥:%s
still_own_repo = 您的帐户仍然是某些仓库的拥有者,您必须先转移或删除它们才能执行删除帐户操作!
[settings]
profile = 个人信息
password = 修改密码
ssh_keys = 管理 SSH 密钥
social = 社交帐号绑定
orgs = 管理组织
delete = 删除帐户
public_profile = 公开信息
profile_desc = 您的邮箱地址将会被公开,并被用于接收帐户的所有提醒和通知。
full_name = 自定义名称
website = 个人网站
location = 所在地区
update_profile = 更新信息
update_profile_success = 您的个人信息已经更新成功!
change_password = 修改密码
old_password = 当前密码
new_password = 新的密码
password_incorrect = 当前密码不正确!
change_password_success = 密码修改成功!您现在可以使用新的密码登录。
manage_ssh_keys = 管理 SSH 密钥
add_key = 增加密钥
ssh_desc = 以下是与您帐户所关联的 SSH 密钥,如果您发现有陌生的密钥,请立即删除它!
ssh_helper = <strong>需要帮助?</strong> 请查看有关 <a href="https://help.github.com/articles/generating-ssh-keys">如何生成 SSH 密钥</a> 或 <a href="https://help.github.com/ssh-issues/">常见 SSH 问题</a> 寻找答案。
add_new_key = 增加 SSH 密钥
key_name = 密钥名称
key_content = 密钥内容
add_key_success = 新的 SSH 密钥添加成功!
delete_key = 删除
add_on = 增加于
last_used = 上次使用在
no_activity = 没有最近活动
manage_orgs = 管理我的组织
manage_social = 管理关联社交帐户
delete_account = 删除当前帐户
delete_prompt = 删除操作会永久清除您的帐户信息,并且 <strong>不可恢复</strong>!
confirm_delete_account = 确认删除帐户
[repo]
owner = 拥有者
repo_name = 仓库名称
repo_name_helper = 伟大的仓库名称一般都较短、令人深刻并且 <strong>独一无二</strong> 的。
visibility = 可见性
visiblity_helper = 本仓库将是 <span class="label label-red label-radius">私有的</span>
repo_desc = 仓库描述
repo_lang = 仓库语言
repo_lang_helper = 请选择 .gitignore 文件
license = 授权许可
license_helper = 请选择授权许可文件
init_readme = 使用 README.md 文件初始化仓库
create_repo = 创建仓库
[action]
create_repo = 创建了仓库 <a href="/%s">%s</a>
commit_repo = 推送了 <a href="/%s/src/%s">%s</a> 分支的代码到 <a href="/%s">%s</a>
create_issue = 创建了工单 <a href="/%s/issues/%s">%s#%s</a>
comment_issue = 评论了工单 <a href="/%s/issues/%s">%s#%s</a>
[tool]
ago = 之前
from_now = 之后
now = 现在
1s = 1 秒%s
1m = 1 分钟%s
1h = 1 小时%s
1d = 1 天%s
1w = 1 周%s
1mon = 1 月%s
1y = 1 年%s
seconds = %d 秒%s
minutes = %d 分钟%s
hours = %d 小时%s
days = %d 天%s
weeks = %d 周%s
months = %d 月%s
years = %d 年%s

@ -1,8 +0,0 @@
[program:gogs]
user=git
command = /home/git/gogs/start.sh
directory = /home/git/gogs
autostart = true
stdout_logfile = /var/gogs.log
stderr_logfile = /var/gogs-error.log
environment=HOME="/home/git"

@ -1,40 +0,0 @@
### Install Gogs With Docker
Deploying gogs in [Docker](http://www.docker.io/) is just as easy as eating a pie, what you do is just open the `dockerfiles/build.sh` file, replace the configs:
```
DB_TYPE="YOUR_DB_TYPE" # type of database, support 'mysql' and 'postgres'
MEM_TYPE="YOUR_MEM_TYPE" # type of memory database, support 'redis' and 'memcache'
DB_PASSWORD="YOUR_DB_PASSWORD" # The database password.
DB_RUN_NAME="YOUR_DB_RUN_NAME" # The --name option value when run the database image.
MEM_RUN_NAME="YOUR_MEM_RUN_NAME" # The --name option value when run the mem database image.
HOST_PORT="YOUR_HOST_PORT" # The port on host, which will be redirected to the port 3000 inside gogs container.
```
And run:
```
cd dockerfiles
./build.sh
```
The build might take some time, just be paient. After it finishes, you will receive the message:
```
Now we have the MySQL image(running) and gogs image, use the follow command to start gogs service( the content might be different, according to your own configs):
docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
```
Just follow the message, run:
```
docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
```
Now we have gogs running! Open the browser and navigate to:
```
http://YOUR_HOST_IP:YOUR_HOST_PORT
```
Let's 'gogs'!
Ouya~

@ -4,7 +4,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language. // Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
package main package main
import ( import (
@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.4.5.0712 Alpha" const APP_VER = "0.4.7.0726 Alpha"
func init() { func init() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())

@ -8,30 +8,31 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"path"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"unicode" "unicode"
"github.com/gogits/git"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
// Operation types of user action. type ActionType int
const ( const (
OP_CREATE_REPO = iota + 1 CREATE_REPO ActionType = iota + 1 // 1
OP_DELETE_REPO DELETE_REPO // 2
OP_STAR_REPO STAR_REPO // 3
OP_FOLLOW_REPO FOLLOW_REPO // 4
OP_COMMIT_REPO COMMIT_REPO // 5
OP_CREATE_ISSUE CREATE_ISSUE // 6
OP_PULL_REQUEST PULL_REQUEST // 7
OP_TRANSFER_REPO TRANSFER_REPO // 8
OP_PUSH_TAG PUSH_TAG // 9
OP_COMMENT_ISSUE COMMENT_ISSUE // 10
) )
var ( var (
@ -53,7 +54,7 @@ func init() {
type Action struct { type Action struct {
Id int64 Id int64
UserId int64 // Receiver user id. UserId int64 // Receiver user id.
OpType int OpType ActionType
ActUserId int64 // Action user id. ActUserId int64 // Action user id.
ActUserName string // Action user name. ActUserName string // Action user name.
ActEmail string ActEmail string
@ -67,7 +68,7 @@ type Action struct {
} }
func (a Action) GetOpType() int { func (a Action) GetOpType() int {
return a.OpType return int(a.OpType)
} }
func (a Action) GetActUserName() string { func (a Action) GetActUserName() string {
@ -86,6 +87,10 @@ func (a Action) GetRepoName() string {
return a.RepoName return a.RepoName
} }
func (a Action) GetRepoLink() string {
return path.Join(a.RepoUserName, a.RepoName)
}
func (a Action) GetBranch() string { func (a Action) GetBranch() string {
return a.RefName return a.RefName
} }
@ -94,6 +99,14 @@ func (a Action) GetContent() string {
return a.Content return a.Content
} }
func (a Action) GetCreate() time.Time {
return a.Created
}
func (a Action) GetIssueInfos() []string {
return strings.SplitN(a.Content, "|", 2)
}
func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, commits []*base.PushCommit) error { func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, commits []*base.PushCommit) error {
for _, c := range commits { for _, c := range commits {
refs := IssueKeywordsPat.FindAllString(c.Message, -1) refs := IssueKeywordsPat.FindAllString(c.Message, -1)
@ -160,12 +173,11 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
// CommitRepoAction adds new action for committing repository. // CommitRepoAction adds new action for committing repository.
func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits) error { repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits) error {
// log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName)
opType := OP_COMMIT_REPO opType := COMMIT_REPO
// Check it's tag push or branch. // Check it's tag push or branch.
if strings.HasPrefix(refFullName, "refs/tags/") { if strings.HasPrefix(refFullName, "refs/tags/") {
opType = OP_PUSH_TAG opType = PUSH_TAG
commit = &base.PushCommits{} commit = &base.PushCommits{}
} }
@ -269,26 +281,26 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
// NewRepoAction adds new action for creating repository. // NewRepoAction adds new action for creating repository.
func NewRepoAction(u *User, repo *Repository) (err error) { func NewRepoAction(u *User, repo *Repository) (err error) {
if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email, if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email,
OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoUserName: repo.Owner.Name, RepoName: repo.Name, OpType: CREATE_REPO, RepoId: repo.Id, RepoUserName: repo.Owner.Name, RepoName: repo.Name,
IsPrivate: repo.IsPrivate}); err != nil { IsPrivate: repo.IsPrivate}); err != nil {
log.Error("action.NewRepoAction(notify watchers): %d/%s", u.Id, repo.Name) log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name)
return err return err
} }
log.Trace("action.NewRepoAction: %s/%s", u.LowerName, repo.LowerName) log.Trace("action.NewRepoAction: %s/%s", u.Name, repo.Name)
return err return err
} }
// TransferRepoAction adds new action for transfering repository. // TransferRepoAction adds new action for transfering repository.
func TransferRepoAction(user, newUser *User, repo *Repository) (err error) { func TransferRepoAction(u, newUser *User, repo *Repository) (err error) {
if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email, if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email,
OpType: OP_TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name, OpType: TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name,
IsPrivate: repo.IsPrivate}); err != nil { IsPrivate: repo.IsPrivate}); err != nil {
log.Error("action.TransferRepoAction(notify watchers): %d/%s", user.Id, repo.Name) log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name)
return err return err
} }
log.Trace("action.TransferRepoAction: %s/%s", user.LowerName, repo.LowerName) log.Trace("action.TransferRepoAction: %s/%s", u.Name, repo.Name)
return err return err
} }

@ -13,9 +13,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/Unknwon/com"
"github.com/gogits/git" "github.com/gogits/git"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process" "github.com/gogits/gogs/modules/process"
) )
@ -118,8 +119,8 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
// Parse line number. // Parse line number.
ranges := strings.Split(ss[len(ss)-2][1:], " ") ranges := strings.Split(ss[len(ss)-2][1:], " ")
leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int() leftLine, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int() rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
continue continue
case line[0] == '+': case line[0] == '+':
curFile.Addition++ curFile.Addition++
@ -214,7 +215,7 @@ func GetDiff(repoPath, commitid string) (*Diff, error) {
select { select {
case <-time.After(5 * time.Minute): case <-time.After(5 * time.Minute):
if errKill := process.Kill(pid); errKill != nil { if errKill := process.Kill(pid); errKill != nil {
log.Error("git_diff.ParsePatch(Kill): %v", err) log.Error(4, "git_diff.ParsePatch(Kill): %v", err)
} }
<-done <-done
// return "", ErrExecTimeout.Error(), ErrExecTimeout // return "", ErrExecTimeout.Error(), ErrExecTimeout

@ -13,9 +13,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
) )
@ -72,7 +72,7 @@ func (i *Issue) GetLabels() error {
strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$") strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
i.Labels = make([]*Label, 0, len(strIds)) i.Labels = make([]*Label, 0, len(strIds))
for _, strId := range strIds { for _, strId := range strIds {
id, _ := base.StrTo(strId).Int64() id, _ := com.StrTo(strId).Int64()
if id > 0 { if id > 0 {
l, err := GetLabelById(id) l, err := GetLabelById(id)
if err != nil { if err != nil {
@ -337,7 +337,7 @@ func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*Issue
buf := bytes.NewBufferString("") buf := bytes.NewBufferString("")
for _, rid := range rids { for _, rid := range rids {
buf.WriteString("repo_id=") buf.WriteString("repo_id=")
buf.WriteString(base.ToStr(rid)) buf.WriteString(com.ToStr(rid))
buf.WriteString(" OR ") buf.WriteString(" OR ")
} }
cond := strings.TrimSuffix(buf.String(), " OR ") cond := strings.TrimSuffix(buf.String(), " OR ")
@ -564,7 +564,7 @@ func UpdateLabel(l *Label) error {
// DeleteLabel delete a label of given repository. // DeleteLabel delete a label of given repository.
func DeleteLabel(repoId int64, strId string) error { func DeleteLabel(repoId int64, strId string) error {
id, _ := base.StrTo(strId).Int64() id, _ := com.StrTo(strId).Int64()
l, err := GetLabelById(id) l, err := GetLabelById(id)
if err != nil { if err != nil {
if err == ErrLabelNotExist { if err == ErrLabelNotExist {
@ -768,7 +768,7 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
m.Completeness = 0 m.Completeness = 0
} }
if _, err = sess.Id(m.Id).Update(m); err != nil { if _, err = sess.Id(m.Id).Cols("num_issues,num_completeness,num_closed_issues").Update(m); err != nil {
sess.Rollback() sess.Rollback()
return err return err
} }
@ -796,7 +796,7 @@ func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
} }
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
if _, err = sess.Id(m.Id).Update(m); err != nil { if _, err = sess.Id(m.Id).Cols("num_issues,num_completeness,num_closed_issues").Update(m); err != nil {
sess.Rollback() sess.Rollback()
return err return err
} }

@ -164,15 +164,14 @@ func UserSignIn(uname, passwd string) (*User, error) {
if u.LoginType == NOTYPE { if u.LoginType == NOTYPE {
if has { if has {
u.LoginType = PLAIN u.LoginType = PLAIN
} else {
return nil, ErrUserNotExist
} }
} }
// for plain login, user must have existed. // For plain login, user must exist to reach this line.
// Now verify password.
if u.LoginType == PLAIN { if u.LoginType == PLAIN {
if !has {
return nil, ErrUserNotExist
}
newUser := &User{Passwd: passwd, Salt: u.Salt} newUser := &User{Passwd: passwd, Salt: u.Salt}
newUser.EncodePasswd() newUser.EncodePasswd()
if u.Passwd != newUser.Passwd { if u.Passwd != newUser.Passwd {
@ -233,18 +232,18 @@ func UserSignIn(uname, passwd string) (*User, error) {
// Query if name/passwd can login against the LDAP direcotry pool // Query if name/passwd can login against the LDAP direcotry pool
// Create a local user if success // Create a local user if success
// Return the same LoginUserPlain semantic // Return the same LoginUserPlain semantic
func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) { func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) {
mail, logged := cfg.Ldapsource.SearchEntry(name, passwd) mail, logged := cfg.Ldapsource.SearchEntry(name, passwd)
if !logged { if !logged {
// user not in LDAP, do nothing // user not in LDAP, do nothing
return nil, ErrUserNotExist return nil, ErrUserNotExist
} }
if !autoRegister { if !autoRegister {
return user, nil return u, nil
} }
// fake a local user creation // fake a local user creation
user = &User{ u = &User{
LowerName: strings.ToLower(name), LowerName: strings.ToLower(name),
Name: strings.ToLower(name), Name: strings.ToLower(name),
LoginType: LDAP, LoginType: LDAP,
@ -255,7 +254,8 @@ func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *L
Email: mail, Email: mail,
} }
return CreateUser(user) err := CreateUser(u)
return u, err
} }
type loginAuth struct { type loginAuth struct {
@ -322,7 +322,7 @@ func SmtpAuth(host string, port int, a smtp.Auth, useTls bool) error {
// Query if name/passwd can login against the LDAP direcotry pool // Query if name/passwd can login against the LDAP direcotry pool
// Create a local user if success // Create a local user if success
// Return the same LoginUserPlain semantic // Return the same LoginUserPlain semantic
func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) { func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
var auth smtp.Auth var auth smtp.Auth
if cfg.Auth == SMTP_PLAIN { if cfg.Auth == SMTP_PLAIN {
auth = smtp.PlainAuth("", name, passwd, cfg.Host) auth = smtp.PlainAuth("", name, passwd, cfg.Host)
@ -340,7 +340,7 @@ func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *S
} }
if !autoRegister { if !autoRegister {
return user, nil return u, nil
} }
var loginName = name var loginName = name
@ -349,7 +349,7 @@ func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *S
loginName = name[:idx] loginName = name[:idx]
} }
// fake a local user creation // fake a local user creation
user = &User{ u = &User{
LowerName: strings.ToLower(loginName), LowerName: strings.ToLower(loginName),
Name: strings.ToLower(loginName), Name: strings.ToLower(loginName),
LoginType: SMTP, LoginType: SMTP,
@ -359,5 +359,6 @@ func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *S
Passwd: passwd, Passwd: passwd,
Email: name, Email: name,
} }
return CreateUser(user) err := CreateUser(u)
return u, err
} }

@ -54,7 +54,7 @@ func exePath() (string, error) {
func homeDir() string { func homeDir() string {
home, err := com.HomeDir() home, err := com.HomeDir()
if err != nil { if err != nil {
log.Fatal("Fail to get home directory: %v", err) log.Fatal(4, "Fail to get home directory: %v", err)
} }
return home return home
} }
@ -63,25 +63,28 @@ func init() {
var err error var err error
if appPath, err = exePath(); err != nil { if appPath, err = exePath(); err != nil {
log.Fatal("publickey.init(fail to get app path): %v\n", err) log.Fatal(4, "fail to get app path: %v\n", err)
} }
appPath = strings.Replace(appPath, "\\", "/", -1)
// Determine and create .ssh path. // Determine and create .ssh path.
SshPath = filepath.Join(homeDir(), ".ssh") SshPath = filepath.Join(homeDir(), ".ssh")
if err = os.MkdirAll(SshPath, os.ModePerm); err != nil { if err = os.MkdirAll(SshPath, os.ModePerm); err != nil {
log.Fatal("publickey.init(fail to create SshPath(%s)): %v\n", SshPath, err) log.Fatal(4, "fail to create SshPath(%s): %v\n", SshPath, err)
} }
} }
// PublicKey represents a SSH key. // PublicKey represents a SSH key.
type PublicKey struct { type PublicKey struct {
Id int64 Id int64
OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
Name string `xorm:"UNIQUE(s) NOT NULL"` Name string `xorm:"UNIQUE(s) NOT NULL"`
Fingerprint string Fingerprint string
Content string `xorm:"TEXT NOT NULL"` Content string `xorm:"TEXT NOT NULL"`
Created time.Time `xorm:"CREATED"` Created time.Time `xorm:"CREATED"`
Updated time.Time `xorm:"UPDATED"` Updated time.Time
HasRecentActivity bool `xorm:"-"`
HasUsed bool `xorm:"-"`
} }
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file. // GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
@ -89,6 +92,59 @@ func (key *PublicKey) GetAuthorizedString() string {
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.Id, key.Content) return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.Id, key.Content)
} }
var (
MinimumKeySize = map[string]int{
"(ED25519)": 256,
"(ECDSA)": 256,
"(NTRU)": 1087,
"(MCE)": 1702,
"(McE)": 1702,
"(RSA)": 2048,
}
)
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
func CheckPublicKeyString(content string) (bool, error) {
if strings.ContainsAny(content, "\n\r") {
return false, errors.New("Only a single line with a single key please")
}
// write the key to a file…
tmpFile, err := ioutil.TempFile(os.TempDir(), "keytest")
if err != nil {
return false, err
}
tmpPath := tmpFile.Name()
defer os.Remove(tmpPath)
tmpFile.WriteString(content)
tmpFile.Close()
// … see if ssh-keygen recognizes its contents
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
if err != nil {
return false, errors.New("ssh-keygen -l -f: " + stderr)
} else if len(stdout) < 2 {
return false, errors.New("ssh-keygen returned not enough output to evaluate the key")
}
sshKeygenOutput := strings.Split(stdout, " ")
if len(sshKeygenOutput) < 4 {
return false, errors.New("Not enough fields returned by ssh-keygen -l -f")
}
keySize, err := com.StrTo(sshKeygenOutput[0]).Int()
if err != nil {
return false, errors.New("Cannot get key size of the given key")
}
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
if minimumKeySize := MinimumKeySize[keyType]; minimumKeySize == 0 {
return false, errors.New("Sorry, unrecognized public key type")
} else if keySize < minimumKeySize {
return false, fmt.Errorf("The minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
}
return true, nil
}
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file. // saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
func saveAuthorizedKeyFile(key *PublicKey) error { func saveAuthorizedKeyFile(key *PublicKey) error {
sshOpLocker.Lock() sshOpLocker.Lock()
@ -144,10 +200,18 @@ func AddPublicKey(key *PublicKey) (err error) {
} }
// ListPublicKey returns a list of all public keys that user has. // ListPublicKey returns a list of all public keys that user has.
func ListPublicKey(uid int64) ([]PublicKey, error) { func ListPublicKey(uid int64) ([]*PublicKey, error) {
keys := make([]PublicKey, 0, 5) keys := make([]*PublicKey, 0, 5)
err := x.Find(&keys, &PublicKey{OwnerId: uid}) err := x.Find(&keys, &PublicKey{OwnerId: uid})
return keys, err if err != nil {
return nil, err
}
for _, key := range keys {
key.HasUsed = key.Updated.After(key.Created)
key.HasRecentActivity = key.Updated.Add(7 * 24 * time.Hour).After(time.Now())
}
return keys, nil
} }
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file. // rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
@ -218,8 +282,6 @@ func DeletePublicKey(key *PublicKey) error {
fpath := filepath.Join(SshPath, "authorized_keys") fpath := filepath.Join(SshPath, "authorized_keys")
tmpPath := filepath.Join(SshPath, "authorized_keys.tmp") tmpPath := filepath.Join(SshPath, "authorized_keys.tmp")
log.Trace("publickey.DeletePublicKey(authorized_keys): %s", fpath)
if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil { if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
return err return err
} else if err = os.Remove(fpath); err != nil { } else if err = os.Remove(fpath); err != nil {

@ -10,7 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/gogits/git" "github.com/gogits/gogs/modules/git"
) )
var ( var (

@ -14,7 +14,6 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -23,10 +22,7 @@ import (
"github.com/Unknwon/cae/zip" "github.com/Unknwon/cae/zip"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/gogits/git" "github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/bin"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/process" "github.com/gogits/gogs/modules/process"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
@ -47,35 +43,27 @@ var (
) )
var ( var (
LanguageIgns, Licenses []string Gitignores, Licenses []string
) )
var ( var (
DescriptionPattern = regexp.MustCompile(`https?://\S+`) DescriptionPattern = regexp.MustCompile(`https?://\S+`)
) )
// getAssetList returns corresponding asset list in 'conf'.
func getAssetList(prefix string) []string {
assets := make([]string, 0, 15)
for _, name := range bin.AssetNames() {
if strings.HasPrefix(name, prefix) {
assets = append(assets, strings.TrimPrefix(name, prefix+"/"))
}
}
return assets
}
func LoadRepoConfig() { func LoadRepoConfig() {
// Load .gitignore and license files. // Load .gitignore and license files.
types := []string{"gitignore", "license"} types := []string{"gitignore", "license"}
typeFiles := make([][]string, 2) typeFiles := make([][]string, 2)
for i, t := range types { for i, t := range types {
files := getAssetList(path.Join("conf", t)) files, err := com.StatDir(path.Join("conf", t))
if err != nil {
log.Fatal(4, "Fail to get %s files: %v", t, err)
}
customPath := path.Join(setting.CustomPath, "conf", t) customPath := path.Join(setting.CustomPath, "conf", t)
if com.IsDir(customPath) { if com.IsDir(customPath) {
customFiles, err := com.StatDir(customPath) customFiles, err := com.StatDir(customPath)
if err != nil { if err != nil {
log.Fatal("Fail to get custom %s files: %v", t, err) log.Fatal(4, "Fail to get custom %s files: %v", t, err)
} }
for _, f := range customFiles { for _, f := range customFiles {
@ -87,34 +75,33 @@ func LoadRepoConfig() {
typeFiles[i] = files typeFiles[i] = files
} }
LanguageIgns = typeFiles[0] Gitignores = typeFiles[0]
Licenses = typeFiles[1] Licenses = typeFiles[1]
sort.Strings(LanguageIgns) sort.Strings(Gitignores)
sort.Strings(Licenses) sort.Strings(Licenses)
} }
func NewRepoContext() { func NewRepoContext() {
zip.Verbose = false zip.Verbose = false
// Check Git version.
ver, err := git.GetVersion()
if err != nil {
log.Fatal(4, "Fail to get Git version: %v", err)
}
if ver.Major < 2 && ver.Minor < 8 {
log.Fatal(4, "Gogs requires Git version greater or equal to 1.8.0")
}
// Check if server has basic git setting. // Check if server has basic git setting.
stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name") stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name")
if err != nil { if err != nil {
log.Fatal("repo.NewRepoContext(fail to get git user.name): %s", stderr) log.Fatal(4, "Fail to get git user.name: %s", stderr)
} else if err != nil || len(strings.TrimSpace(stdout)) == 0 { } else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
if _, stderr, err = process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil { if _, stderr, err = process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
log.Fatal("repo.NewRepoContext(fail to set git user.email): %s", stderr) log.Fatal(4, "Fail to set git user.email: %s", stderr)
} else if _, stderr, err = process.Exec("NewRepoContext(set name)", "git", "config", "--global", "user.name", "Gogs"); err != nil { } else if _, stderr, err = process.Exec("NewRepoContext(set name)", "git", "config", "--global", "user.name", "Gogs"); err != nil {
log.Fatal("repo.NewRepoContext(fail to set git user.name): %s", stderr) log.Fatal(4, "Fail to set git user.name: %s", stderr)
}
}
barePath := path.Join(setting.RepoRootPath, "git-bare.zip")
if !com.IsExist(barePath) {
data, err := bin.Asset("conf/content/git-bare.zip")
if err != nil {
log.Fatal("Fail to get asset 'git-bare.zip': %v", err)
} else if err := ioutil.WriteFile(barePath, data, os.ModePerm); err != nil {
log.Fatal("Fail to write asset 'git-bare.zip': %v", err)
} }
} }
} }
@ -135,12 +122,16 @@ type Repository struct {
NumIssues int NumIssues int
NumClosedIssues int NumClosedIssues int
NumOpenIssues int `xorm:"-"` NumOpenIssues int `xorm:"-"`
NumPulls int
NumClosedPulls int
NumOpenPulls int `xorm:"-"`
NumMilestones int `xorm:"NOT NULL DEFAULT 0"` NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumOpenMilestones int `xorm:"-"` NumOpenMilestones int `xorm:"-"`
NumTags int `xorm:"-"` NumTags int `xorm:"-"`
IsPrivate bool IsPrivate bool
IsMirror bool IsMirror bool
IsFork bool `xorm:"NOT NULL DEFAULT false"`
IsBare bool IsBare bool
IsGoget bool IsGoget bool
DefaultBranch string DefaultBranch string
@ -153,11 +144,11 @@ func (repo *Repository) GetOwner() (err error) {
return err return err
} }
// DescriptionHtml does special handles to description and return HTML string.
func (repo *Repository) DescriptionHtml() template.HTML { func (repo *Repository) DescriptionHtml() template.HTML {
sanitize := func(s string) string { sanitize := func(s string) string {
// TODO(nuss-justin): Improve sanitization. Strip all tags? // TODO(nuss-justin): Improve sanitization. Strip all tags?
ss := html.EscapeString(s) ss := html.EscapeString(s)
return fmt.Sprintf(`<a href="%s" target="_blank">%s</a>`, ss, ss) return fmt.Sprintf(`<a href="%s" target="_blank">%s</a>`, ss, ss)
} }
return template.HTML(DescriptionPattern.ReplaceAllStringFunc(repo.Description, sanitize)) return template.HTML(DescriptionPattern.ReplaceAllStringFunc(repo.Description, sanitize))
@ -225,7 +216,8 @@ func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) er
return err return err
} }
return git.UnpackRefs(repoPath) // return git.UnpackRefs(repoPath)
return nil
} }
func GetMirror(repoId int64) (*Mirror, error) { func GetMirror(repoId int64) (*Mirror, error) {
@ -257,14 +249,14 @@ func MirrorUpdate() {
repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath), repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
"git", "remote", "update"); err != nil { "git", "remote", "update"); err != nil {
return errors.New("git remote update: " + stderr) return errors.New("git remote update: " + stderr)
} else if err = git.UnpackRefs(repoPath); err != nil { } // else if err = git.UnpackRefs(repoPath); err != nil {
return errors.New("UnpackRefs: " + err.Error()) // return errors.New("UnpackRefs: " + err.Error())
} // }
m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour) m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour)
return UpdateMirror(m) return UpdateMirror(m)
}); err != nil { }); err != nil {
log.Error("repo.MirrorUpdate: %v", err) log.Error(4, "repo.MirrorUpdate: %v", err)
} }
} }
@ -317,7 +309,7 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
// extractGitBareZip extracts git-bare.zip to repository path. // extractGitBareZip extracts git-bare.zip to repository path.
func extractGitBareZip(repoPath string) error { func extractGitBareZip(repoPath string) error {
z, err := zip.Open(filepath.Join(setting.RepoRootPath, "git-bare.zip")) z, err := zip.Open(path.Join(setting.ConfRootPath, "content/git-bare.zip"))
if err != nil { if err != nil {
return err return err
} }
@ -361,34 +353,18 @@ func createHookUpdate(hookPath, content string) error {
return err return err
} }
// SetRepoEnvs sets environment variables for command update.
func SetRepoEnvs(userId int64, userName, repoName, repoUserName string) {
os.Setenv("userId", base.ToStr(userId))
os.Setenv("userName", userName)
os.Setenv("repoName", repoName)
os.Setenv("repoUserName", repoUserName)
}
// InitRepository initializes README and .gitignore if needed. // InitRepository initializes README and .gitignore if needed.
func initRepository(f string, user *User, repo *Repository, initReadme bool, repoLang, license string) error { func initRepository(f string, u *User, repo *Repository, initReadme bool, repoLang, license string) error {
repoPath := RepoPath(user.Name, repo.Name) repoPath := RepoPath(u.Name, repo.Name)
// Create bare new repository. // Create bare new repository.
if err := extractGitBareZip(repoPath); err != nil { if err := extractGitBareZip(repoPath); err != nil {
return err return err
} }
if runtime.GOOS == "windows" {
rp := strings.NewReplacer("\\", "/")
appPath = "\"" + rp.Replace(appPath) + "\""
} else {
rp := strings.NewReplacer("\\", "/", " ", "\\ ")
appPath = rp.Replace(appPath)
}
// hook/post-update // hook/post-update
if err := createHookUpdate(filepath.Join(repoPath, "hooks", "update"), if err := createHookUpdate(filepath.Join(repoPath, "hooks", "update"),
fmt.Sprintf(TPL_UPDATE_HOOK, setting.ScriptType, appPath)); err != nil { fmt.Sprintf(TPL_UPDATE_HOOK, setting.ScriptType, "\""+appPath+"\"")); err != nil {
return err return err
} }
@ -405,7 +381,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
} }
// Clone to temprory path and do the init commit. // Clone to temprory path and do the init commit.
tmpDir := filepath.Join(os.TempDir(), base.ToStr(time.Now().Nanosecond())) tmpDir := filepath.Join(os.TempDir(), com.ToStr(time.Now().Nanosecond()))
os.MkdirAll(tmpDir, os.ModePerm) os.MkdirAll(tmpDir, os.ModePerm)
_, stderr, err := process.Exec( _, stderr, err := process.Exec(
@ -426,12 +402,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
} }
// .gitignore // .gitignore
if repoLang != "" { filePath := "conf/gitignore/" + repoLang
filePath := "conf/gitignore/" + repoLang if com.IsFile(filePath) {
targetPath := path.Join(tmpDir, fileName["gitign"]) targetPath := path.Join(tmpDir, fileName["gitign"])
data, err := bin.Asset(filePath) if com.IsFile(filePath) {
if err == nil { if err = com.Copy(filePath, targetPath); err != nil {
if err = ioutil.WriteFile(targetPath, data, os.ModePerm); err != nil {
return err return err
} }
} else { } else {
@ -443,15 +418,16 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
} }
} }
} }
} else {
delete(fileName, "gitign")
} }
// LICENSE // LICENSE
if license != "" { filePath = "conf/license/" + license
filePath := "conf/license/" + license if com.IsFile(filePath) {
targetPath := path.Join(tmpDir, fileName["license"]) targetPath := path.Join(tmpDir, fileName["license"])
data, err := bin.Asset(filePath) if com.IsFile(filePath) {
if err == nil { if err = com.Copy(filePath, targetPath); err != nil {
if err = ioutil.WriteFile(targetPath, data, os.ModePerm); err != nil {
return err return err
} }
} else { } else {
@ -463,16 +439,16 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
} }
} }
} }
} else {
delete(fileName, "license")
} }
if len(fileName) == 0 { if len(fileName) == 0 {
return nil return nil
} }
SetRepoEnvs(user.Id, user.Name, repo.Name, user.Name)
// Apply changes and commit. // Apply changes and commit.
return initRepoCommit(tmpDir, user.NewGitSig()) return initRepoCommit(tmpDir, u.NewGitSig())
} }
// CreateRepository creates a repository for given user or organization. // CreateRepository creates a repository for given user or organization.
@ -549,15 +525,15 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror
} }
} }
rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?" if _, err = sess.Exec(
if _, err = sess.Exec(rawSql, u.Id); err != nil { "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil {
sess.Rollback() sess.Rollback()
return nil, err return nil, err
} }
// Update owner team info and count. // Update owner team info and count.
if u.IsOrganization() { if u.IsOrganization() {
t.RepoIds += "$" + base.ToStr(repo.Id) + "|" t.RepoIds += "$" + com.ToStr(repo.Id) + "|"
t.NumRepos++ t.NumRepos++
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
sess.Rollback() sess.Rollback()
@ -572,24 +548,24 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror
if u.IsOrganization() { if u.IsOrganization() {
ous, err := GetOrgUsersByOrgId(u.Id) ous, err := GetOrgUsersByOrgId(u.Id)
if err != nil { if err != nil {
log.Error("repo.CreateRepository(GetOrgUsersByOrgId): %v", err) log.Error(4, "repo.CreateRepository(GetOrgUsersByOrgId): %v", err)
} else { } else {
for _, ou := range ous { for _, ou := range ous {
if err = WatchRepo(ou.Uid, repo.Id, true); err != nil { if err = WatchRepo(ou.Uid, repo.Id, true); err != nil {
log.Error("repo.CreateRepository(WatchRepo): %v", err) log.Error(4, "repo.CreateRepository(WatchRepo): %v", err)
} }
} }
} }
} }
if err = WatchRepo(u.Id, repo.Id, true); err != nil { if err = WatchRepo(u.Id, repo.Id, true); err != nil {
log.Error("repo.CreateRepository(WatchRepo2): %v", err) log.Error(4, "WatchRepo2: %v", err)
} }
if err = NewRepoAction(u, repo); err != nil { if err = NewRepoAction(u, repo); err != nil {
log.Error("repo.CreateRepository(NewRepoAction): %v", err) log.Error(4, "NewRepoAction: %v", err)
} }
// No need for init for mirror. // No need for init mirror.
if mirror { if mirror {
return repo, nil return repo, nil
} }
@ -597,11 +573,11 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror
repoPath := RepoPath(u.Name, repo.Name) repoPath := RepoPath(u.Name, repo.Name)
if err = initRepository(repoPath, u, repo, initReadme, lang, license); err != nil { if err = initRepository(repoPath, u, repo, initReadme, lang, license); err != nil {
if err2 := os.RemoveAll(repoPath); err2 != nil { if err2 := os.RemoveAll(repoPath); err2 != nil {
log.Error("repo.CreateRepository(initRepository): %v", err) log.Error(4, "initRepository: %v", err)
return nil, errors.New(fmt.Sprintf( return nil, fmt.Errorf(
"delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2)) "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2)
} }
return nil, err return nil, fmt.Errorf("initRepository: %v", err)
} }
_, stderr, err := process.ExecDir(-1, _, stderr, err := process.ExecDir(-1,
@ -982,15 +958,12 @@ func WatchRepo(uid, rid int64, watch bool) (err error) {
if _, err = x.Insert(&Watch{RepoId: rid, UserId: uid}); err != nil { if _, err = x.Insert(&Watch{RepoId: rid, UserId: uid}); err != nil {
return err return err
} }
_, err = x.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", rid)
rawSql := "UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?"
_, err = x.Exec(rawSql, rid)
} else { } else {
if _, err = x.Delete(&Watch{0, uid, rid}); err != nil { if _, err = x.Delete(&Watch{0, uid, rid}); err != nil {
return err return err
} }
rawSql := "UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?" _, err = x.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", rid)
_, err = x.Exec(rawSql, rid)
} }
return err return err
} }

@ -10,9 +10,8 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/gogits/git"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
) )
@ -47,8 +46,6 @@ func DelUpdateTasksByUuid(uuid string) error {
} }
func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error { func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error {
//fmt.Println(refName, oldCommitId, newCommitId)
//fmt.Println(userName, repoUserName, repoName)
isNew := strings.HasPrefix(oldCommitId, "0000000") isNew := strings.HasPrefix(oldCommitId, "0000000")
if isNew && if isNew &&
strings.HasPrefix(newCommitId, "0000000") { strings.HasPrefix(newCommitId, "0000000") {
@ -82,12 +79,12 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err) return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
} }
// if tags push // Push tags.
if strings.HasPrefix(refName, "refs/tags/") { if strings.HasPrefix(refName, "refs/tags/") {
tagName := git.RefEndName(refName) tagName := git.RefEndName(refName)
tag, err := repo.GetTag(tagName) tag, err := repo.GetTag(tagName)
if err != nil { if err != nil {
log.GitLogger.Fatal("runUpdate.GetTag: %v", err) log.GitLogger.Fatal(4, "runUpdate.GetTag: %v", err)
} }
var actEmail string var actEmail string
@ -96,7 +93,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
} else { } else {
cmt, err := tag.Commit() cmt, err := tag.Commit()
if err != nil { if err != nil {
log.GitLogger.Fatal("runUpdate.GetTag Commit: %v", err) log.GitLogger.Fatal(4, "runUpdate.GetTag Commit: %v", err)
} }
actEmail = cmt.Committer.Email actEmail = cmt.Committer.Email
} }
@ -105,7 +102,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
if err = CommitRepoAction(userId, ru.Id, userName, actEmail, if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
repos.Id, repoUserName, repoName, refName, commit); err != nil { repos.Id, repoUserName, repoName, refName, commit); err != nil {
log.GitLogger.Fatal("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err) log.GitLogger.Fatal(4, "runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
} }
return err return err
} }
@ -135,7 +132,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
// if commits push // if commits push
commits := make([]*base.PushCommit, 0) commits := make([]*base.PushCommit, 0)
var maxCommits = 3 var maxCommits = 2
var actEmail string var actEmail string
for e := l.Front(); e != nil; e = e.Next() { for e := l.Front(); e != nil; e = e.Next() {
commit := e.Value.(*git.Commit) commit := e.Value.(*git.Commit)

@ -14,9 +14,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/gogits/git" "github.com/Unknwon/com"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
@ -77,6 +78,14 @@ type User struct {
Members []*User `xorm:"-"` Members []*User `xorm:"-"`
} }
// DashboardLink returns the user dashboard page link.
func (u *User) DashboardLink() string {
if u.IsOrganization() {
return "/org/" + u.Name + "/dashboard"
}
return "/"
}
// HomeLink returns the user home page link. // HomeLink returns the user home page link.
func (u *User) HomeLink() string { func (u *User) HomeLink() string {
return "/user/" + u.Name return "/user/" + u.Name
@ -157,23 +166,23 @@ func GetUserSalt() string {
} }
// CreateUser creates record of a new user. // CreateUser creates record of a new user.
func CreateUser(u *User) (*User, error) { func CreateUser(u *User) error {
if !IsLegalName(u.Name) { if !IsLegalName(u.Name) {
return nil, ErrUserNameIllegal return ErrUserNameIllegal
} }
isExist, err := IsUserExist(u.Name) isExist, err := IsUserExist(u.Name)
if err != nil { if err != nil {
return nil, err return err
} else if isExist { } else if isExist {
return nil, ErrUserAlreadyExist return ErrUserAlreadyExist
} }
isExist, err = IsEmailUsed(u.Email) isExist, err = IsEmailUsed(u.Email)
if err != nil { if err != nil {
return nil, err return err
} else if isExist { } else if isExist {
return nil, ErrEmailAlreadyUsed return ErrEmailAlreadyUsed
} }
u.LowerName = strings.ToLower(u.Name) u.LowerName = strings.ToLower(u.Name)
@ -186,21 +195,17 @@ func CreateUser(u *User) (*User, error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err = sess.Begin(); err != nil { if err = sess.Begin(); err != nil {
return nil, err return err
} }
if _, err = sess.Insert(u); err != nil { if _, err = sess.Insert(u); err != nil {
sess.Rollback() sess.Rollback()
return nil, err return err
} } else if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil {
if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil {
sess.Rollback() sess.Rollback()
return nil, err return err
} } else if err = sess.Commit(); err != nil {
return err
if err = sess.Commit(); err != nil {
return nil, err
} }
// Auto-set admin for user whose ID is 1. // Auto-set admin for user whose ID is 1.
@ -209,7 +214,7 @@ func CreateUser(u *User) (*User, error) {
u.IsActive = true u.IsActive = true
_, err = x.Id(u.Id).UseBool().Update(u) _, err = x.Id(u.Id).UseBool().Update(u)
} }
return u, err return err
} }
// CountUsers returns number of users. // CountUsers returns number of users.
@ -237,7 +242,7 @@ func getVerifyUser(code string) (user *User) {
if user, err = GetUserByName(string(b)); user != nil { if user, err = GetUserByName(string(b)); user != nil {
return user return user
} }
log.Error("user.getVerifyUser: %v", err) log.Error(4, "user.getVerifyUser: %v", err)
} }
return nil return nil
@ -250,7 +255,7 @@ func VerifyUserActiveCode(code string) (user *User) {
if user = getVerifyUser(code); user != nil { if user = getVerifyUser(code); user != nil {
// time limit code // time limit code
prefix := code[:base.TimeLimitCodeLength] prefix := code[:base.TimeLimitCodeLength]
data := base.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands data := com.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
if base.VerifyTimeLimitCode(data, minutes, prefix) { if base.VerifyTimeLimitCode(data, minutes, prefix) {
return user return user
@ -260,12 +265,16 @@ func VerifyUserActiveCode(code string) (user *User) {
} }
// ChangeUserName changes all corresponding setting from old user name to new one. // ChangeUserName changes all corresponding setting from old user name to new one.
func ChangeUserName(user *User, newUserName string) (err error) { func ChangeUserName(u *User, newUserName string) (err error) {
if !IsLegalName(newUserName) {
return ErrUserNameIllegal
}
newUserName = strings.ToLower(newUserName) newUserName = strings.ToLower(newUserName)
// Update accesses of user. // Update accesses of user.
accesses := make([]Access, 0, 10) accesses := make([]Access, 0, 10)
if err = x.Find(&accesses, &Access{UserName: user.LowerName}); err != nil { if err = x.Find(&accesses, &Access{UserName: u.LowerName}); err != nil {
return err return err
} }
@ -277,28 +286,28 @@ func ChangeUserName(user *User, newUserName string) (err error) {
for i := range accesses { for i := range accesses {
accesses[i].UserName = newUserName accesses[i].UserName = newUserName
if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") { if strings.HasPrefix(accesses[i].RepoName, u.LowerName+"/") {
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1) accesses[i].RepoName = strings.Replace(accesses[i].RepoName, u.LowerName, newUserName, 1)
} }
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil { if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err return err
} }
} }
repos, err := GetRepositories(user.Id, true) repos, err := GetRepositories(u.Id, true)
if err != nil { if err != nil {
return err return err
} }
for i := range repos { for i := range repos {
accesses = make([]Access, 0, 10) accesses = make([]Access, 0, 10)
// Update accesses of user repository. // Update accesses of user repository.
if err = x.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repos[i].LowerName}); err != nil { if err = x.Find(&accesses, &Access{RepoName: u.LowerName + "/" + repos[i].LowerName}); err != nil {
return err return err
} }
for j := range accesses { for j := range accesses {
// if the access is not the user's access (already updated above) // if the access is not the user's access (already updated above)
if accesses[j].UserName != user.LowerName { if accesses[j].UserName != u.LowerName {
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil { if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
return err return err
@ -308,7 +317,7 @@ func ChangeUserName(user *User, newUserName string) (err error) {
} }
// Change user directory name. // Change user directory name.
if err = os.Rename(UserPath(user.LowerName), UserPath(newUserName)); err != nil { if err = os.Rename(UserPath(u.LowerName), UserPath(newUserName)); err != nil {
sess.Rollback() sess.Rollback()
return err return err
} }
@ -317,7 +326,7 @@ func ChangeUserName(user *User, newUserName string) (err error) {
} }
// UpdateUser updates user's information. // UpdateUser updates user's information.
func UpdateUser(u *User) (err error) { func UpdateUser(u *User) error {
u.LowerName = strings.ToLower(u.Name) u.LowerName = strings.ToLower(u.Name)
if len(u.Location) > 255 { if len(u.Location) > 255 {
@ -330,7 +339,7 @@ func UpdateUser(u *User) (err error) {
u.Description = u.Description[:255] u.Description = u.Description[:255]
} }
_, err = x.Id(u.Id).AllCols().Update(u) _, err := x.Id(u.Id).AllCols().Update(u)
return err return err
} }
@ -340,7 +349,7 @@ func DeleteUser(u *User) error {
// Check ownership of repository. // Check ownership of repository.
count, err := GetRepositoryCount(u) count, err := GetRepositoryCount(u)
if err != nil { if err != nil {
return errors.New("modesl.GetRepositories(GetRepositoryCount): " + err.Error()) return errors.New("GetRepositoryCount: " + err.Error())
} else if count > 0 { } else if count > 0 {
return ErrUserOwnRepos return ErrUserOwnRepos
} }
@ -562,3 +571,45 @@ func UnFollowUser(userId int64, unFollowId int64) (err error) {
} }
return session.Commit() return session.Commit()
} }
func UpdateMentions(userNames []string, issueId int64) error {
users := make([]*User, 0, len(userNames))
if err := x.Where("name IN (?)", strings.Join(userNames, "\",\"")).OrderBy("name ASC").Find(&users); err != nil {
return err
}
ids := make([]int64, 0, len(userNames))
for _, user := range users {
ids = append(ids, user.Id)
if user.Type == INDIVIDUAL {
continue
}
if user.NumMembers == 0 {
continue
}
tempIds := make([]int64, 0, user.NumMembers)
orgUsers, err := GetOrgUsersByOrgId(user.Id)
if err != nil {
return err
}
for _, orgUser := range orgUsers {
tempIds = append(tempIds, orgUser.Id)
}
ids = append(ids, tempIds...)
}
if err := UpdateIssueUserPairsByMentions(ids, issueId); err != nil {
return err
}
return nil
}

@ -47,7 +47,7 @@ type Webhook struct {
func (w *Webhook) GetEvent() { func (w *Webhook) GetEvent() {
w.HookEvent = &HookEvent{} w.HookEvent = &HookEvent{}
if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil { if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
log.Error("webhook.GetEvent(%d): %v", w.Id, err) log.Error(4, "webhook.GetEvent(%d): %v", w.Id, err)
} }
} }
@ -193,13 +193,13 @@ func DeliverHooks() {
// Only support JSON now. // Only support JSON now.
if _, err := httplib.Post(t.Url).SetTimeout(timeout, timeout). if _, err := httplib.Post(t.Url).SetTimeout(timeout, timeout).
Body([]byte(t.PayloadContent)).Response(); err != nil { Body([]byte(t.PayloadContent)).Response(); err != nil {
log.Error("webhook.DeliverHooks(Delivery): %v", err) log.Error(4, "webhook.DeliverHooks(Delivery): %v", err)
return nil return nil
} }
t.IsDeliveried = true t.IsDeliveried = true
if err := UpdateHookTask(t); err != nil { if err := UpdateHookTask(t); err != nil {
log.Error("webhook.DeliverHooks(UpdateHookTask): %v", err) log.Error(4, "webhook.DeliverHooks(UpdateHookTask): %v", err)
return nil return nil
} }

@ -5,12 +5,9 @@
package auth package auth
import ( import (
"net/http" "github.com/Unknwon/macaron"
"reflect" "github.com/macaron-contrib/i18n"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware/binding" "github.com/gogits/gogs/modules/middleware/binding"
) )
@ -25,17 +22,6 @@ type AdminEditUserForm struct {
LoginType int `form:"login_type"` LoginType int `form:"login_type"`
} }
func (f *AdminEditUserForm) Name(field string) string { func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
names := map[string]string{ validate(errs, ctx.Data, f, l)
"Email": "E-mail address",
"Website": "Website",
"Location": "Location",
"Avatar": "Gravatar Email",
}
return names[field]
}
func (f *AdminEditUserForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
} }

@ -5,13 +5,12 @@
package apiv1 package apiv1
import ( import (
"net/http"
"reflect" "reflect"
"github.com/go-martini/martini" "github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
"github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware/binding" "github.com/gogits/gogs/modules/middleware/binding"
) )
@ -22,17 +21,16 @@ type MarkdownForm struct {
Context string `form:"context"` Context string `form:"context"`
} }
func (f *MarkdownForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) { func (f *MarkdownForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData) validateApiReq(errs, ctx.Data, f, l)
validateApiReq(errs, data, f)
} }
func validateApiReq(errs *binding.Errors, data base.TmplData, f interface{}) { func validateApiReq(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) {
if errs.Count() == 0 { if errs.Count() == 0 {
return return
} else if len(errs.Overall) > 0 { } else if len(errs.Overall) > 0 {
for _, err := range errs.Overall { for _, err := range errs.Overall {
log.Error("%s: %v", reflect.TypeOf(f), err) log.Error(4, "%s: %v", reflect.TypeOf(f), err)
} }
return return
} }

@ -7,49 +7,152 @@ package auth
import ( import (
"net/http" "net/http"
"reflect" "reflect"
"strings"
"github.com/go-martini/martini" "github.com/macaron-contrib/i18n"
"github.com/macaron-contrib/session"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware/binding" "github.com/gogits/gogs/modules/middleware/binding"
"github.com/gogits/gogs/modules/setting"
) )
type AuthenticationForm struct { // SignedInId returns the id of signed in user.
Id int64 `form:"id"` func SignedInId(header http.Header, sess session.Store) int64 {
Type int `form:"type"` if !models.HasEngine {
AuthName string `form:"name" binding:"Required;MaxSize(50)"` return 0
Domain string `form:"domain"` }
Host string `form:"host"`
Port int `form:"port"` if setting.Service.EnableReverseProxyAuth {
UseSSL bool `form:"usessl"` webAuthUser := header.Get(setting.ReverseProxyAuthUser)
BaseDN string `form:"base_dn"` if len(webAuthUser) > 0 {
Attributes string `form:"attributes"` u, err := models.GetUserByName(webAuthUser)
Filter string `form:"filter"` if err != nil {
MsAdSA string `form:"ms_ad_sa"` if err != models.ErrUserNotExist {
IsActived bool `form:"is_actived"` log.Error(4, "GetUserByName: %v", err)
SmtpAuth string `form:"smtpauth"` }
SmtpHost string `form:"smtphost"` return 0
SmtpPort int `form:"smtpport"` }
Tls bool `form:"tls"` return u.Id
AllowAutoRegister bool `form:"allowautoregister"` }
}
uid := sess.Get("uid")
if uid == nil {
return 0
}
if id, ok := uid.(int64); ok {
if _, err := models.GetUserById(id); err != nil {
if err != models.ErrUserNotExist {
log.Error(4, "GetUserById: %v", err)
}
return 0
}
return id
}
return 0
} }
func (f *AuthenticationForm) Name(field string) string { // SignedInUser returns the user object of signed user.
names := map[string]string{ func SignedInUser(header http.Header, sess session.Store) *models.User {
"AuthName": "Authentication's name", uid := SignedInId(header, sess)
"Domain": "Domain name", if uid <= 0 {
"Host": "Host address", return nil
"Port": "Port Number", }
"UseSSL": "Use SSL",
"BaseDN": "Base DN", u, err := models.GetUserById(uid)
"Attributes": "Search attributes", if err != nil {
"Filter": "Search filter", log.Error(4, "GetUserById: %v", err)
"MsAdSA": "Ms Ad SA", return nil
} }
return names[field] return u
} }
func (f *AuthenticationForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) { // AssignForm assign form values back to the template data.
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData) func AssignForm(form interface{}, data map[string]interface{}) {
validate(errors, data, f) typ := reflect.TypeOf(form)
val := reflect.ValueOf(form)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldName := field.Tag.Get("form")
// Allow ignored fields in the struct
if fieldName == "-" {
continue
}
data[fieldName] = val.Field(i).Interface()
}
}
func GetMinMaxSize(field reflect.StructField) string {
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
if strings.HasPrefix(rule, "MinSize(") || strings.HasPrefix(rule, "MaxSize(") {
return rule[8 : len(rule)-1]
}
}
return ""
}
func validate(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) {
if errs.Count() == 0 {
return
} else if len(errs.Overall) > 0 {
for _, err := range errs.Overall {
log.Error(4, "%s: %v", reflect.TypeOf(f), err)
}
return
}
data["HasError"] = true
AssignForm(f, data)
typ := reflect.TypeOf(f)
val := reflect.ValueOf(f)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldName := field.Tag.Get("form")
// Allow ignored fields in the struct
if fieldName == "-" {
continue
}
if err, ok := errs.Fields[field.Name]; ok {
data["Err_"+field.Name] = true
trName := l.Tr("form." + field.Name)
switch err {
case binding.BindingRequireError:
data["ErrorMsg"] = trName + l.Tr("form.require_error")
case binding.BindingAlphaDashError:
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
case binding.BindingAlphaDashDotError:
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
case binding.BindingMinSizeError:
data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinMaxSize(field))
case binding.BindingMaxSizeError:
data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMinMaxSize(field))
case binding.BindingEmailError:
data["ErrorMsg"] = trName + l.Tr("form.email_error")
case binding.BindingUrlError:
data["ErrorMsg"] = trName + l.Tr("form.url_error")
default:
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + err
}
return
}
}
} }

@ -0,0 +1,36 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
"github.com/gogits/gogs/modules/middleware/binding"
)
type AuthenticationForm struct {
Id int64 `form:"id"`
Type int `form:"type"`
AuthName string `form:"name" binding:"Required;MaxSize(50)"`
Domain string `form:"domain"`
Host string `form:"host"`
Port int `form:"port"`
UseSSL bool `form:"usessl"`
BaseDN string `form:"base_dn"`
Attributes string `form:"attributes"`
Filter string `form:"filter"`
MsAdSA string `form:"ms_ad_sa"`
IsActived bool `form:"is_actived"`
SmtpAuth string `form:"smtpauth"`
SmtpHost string `form:"smtphost"`
SmtpPort int `form:"smtpport"`
Tls bool `form:"tls"`
AllowAutoRegister bool `form:"allowautoregister"`
}
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}

@ -55,7 +55,7 @@ func LoginUser(name, passwd string) (a string, r bool) {
func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) { func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) {
l, err := ldapDial(ls) l, err := ldapDial(ls)
if err != nil { if err != nil {
log.Error("LDAP Connect error, %s:%v", ls.Host, err) log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
ls.Enabled = false ls.Enabled = false
return "", false return "", false
} }

@ -5,12 +5,9 @@
package auth package auth
import ( import (
"net/http" "github.com/Unknwon/macaron"
"reflect" "github.com/macaron-contrib/i18n"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware/binding" "github.com/gogits/gogs/modules/middleware/binding"
) )
@ -26,17 +23,8 @@ type CreateOrgForm struct {
Email string `form:"email" binding:"Required;Email;MaxSize(50)"` Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
} }
func (f *CreateOrgForm) Name(field string) string { func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
names := map[string]string{ validate(errs, ctx.Data, f, l)
"OrgName": "Organization name",
"Email": "E-mail address",
}
return names[field]
}
func (f *CreateOrgForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errs, data, f)
} }
type OrgSettingForm struct { type OrgSettingForm struct {
@ -47,20 +35,8 @@ type OrgSettingForm struct {
Location string `form:"location" binding:"MaxSize(50)"` Location string `form:"location" binding:"MaxSize(50)"`
} }
func (f *OrgSettingForm) Name(field string) string { func (f *OrgSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
names := map[string]string{ validate(errs, ctx.Data, f, l)
"DisplayName": "Display name",
"Email": "E-mail address",
"Description": "Description",
"Website": "Website address",
"Location": "Location",
}
return names[field]
}
func (f *OrgSettingForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
} }
// ___________ // ___________
@ -76,15 +52,6 @@ type CreateTeamForm struct {
Permission string `form:"permission"` Permission string `form:"permission"`
} }
func (f *CreateTeamForm) Name(field string) string { func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
names := map[string]string{ validate(errs, ctx.Data, f, l)
"TeamName": "Team name",
"Description": "Team description",
}
return names[field]
}
func (f *CreateTeamForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errs, data, f)
} }

@ -1,33 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"net/http"
"reflect"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware/binding"
)
type AddSSHKeyForm struct {
KeyName string `form:"keyname" binding:"Required"`
KeyContent string `form:"key_content" binding:"Required"`
}
func (f *AddSSHKeyForm) Name(field string) string {
names := map[string]string{
"KeyName": "SSH key name",
"KeyContent": "SSH key content",
}
return names[field]
}
func (f *AddSSHKeyForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}

@ -0,0 +1,21 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
"github.com/gogits/gogs/modules/middleware/binding"
)
type AddSSHKeyForm struct {
SSHTitle string `form:"title" binding:"Required"`
Content string `form:"content" binding:"Required"`
}
func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}

@ -1,252 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"net/http"
"reflect"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware/binding"
)
// __________ .__ __
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
// \/ \/|__| \/ \/
type CreateRepoForm struct {
Uid int64 `form:"uid" binding:"Required"`
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
Private bool `form:"private"`
Description string `form:"desc" binding:"MaxSize(255)"`
Language string `form:"language"`
License string `form:"license"`
InitReadme bool `form:"initReadme"`
}
func (f *CreateRepoForm) Name(field string) string {
names := map[string]string{
"RepoName": "Repository name",
"Description": "Description",
}
return names[field]
}
func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
type MigrateRepoForm struct {
Url string `form:"url" binding:"Url"`
AuthUserName string `form:"auth_username"`
AuthPasswd string `form:"auth_password"`
Uid int64 `form:"uid" binding:"Required"`
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
Mirror bool `form:"mirror"`
Private bool `form:"private"`
Description string `form:"desc" binding:"MaxSize(255)"`
}
func (f *MigrateRepoForm) Name(field string) string {
names := map[string]string{
"Url": "Migration URL",
"RepoName": "Repository name",
"Description": "Description",
}
return names[field]
}
func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
type RepoSettingForm struct {
RepoName string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
Description string `form:"desc" binding:"MaxSize(255)"`
Website string `form:"site" binding:"Url;MaxSize(100)"`
Branch string `form:"branch"`
Interval int `form:"interval"`
Private bool `form:"private"`
GoGet bool `form:"goget"`
}
func (f *RepoSettingForm) Name(field string) string {
names := map[string]string{
"RepoName": "Repository name",
"Description": "Description",
"Website": "Website address",
}
return names[field]
}
func (f *RepoSettingForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
// __ __ ___. .__ .__ __
// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
// \/ \/ \/ \/ \/ \/
type NewWebhookForm struct {
Url string `form:"url" binding:"Required;Url"`
ContentType string `form:"content_type" binding:"Required"`
Secret string `form:"secret""`
PushOnly bool `form:"push_only"`
Active bool `form:"active"`
}
func (f *NewWebhookForm) Name(field string) string {
names := map[string]string{
"Url": "Payload URL",
"ContentType": "Content type",
}
return names[field]
}
func (f *NewWebhookForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
// .___
// | | ______ ________ __ ____
// | |/ ___// ___/ | \_/ __ \
// | |\___ \ \___ \| | /\ ___/
// |___/____ >____ >____/ \___ >
// \/ \/ \/
type CreateIssueForm struct {
IssueName string `form:"title" binding:"Required;MaxSize(50)"`
MilestoneId int64 `form:"milestoneid"`
AssigneeId int64 `form:"assigneeid"`
Labels string `form:"labels"`
Content string `form:"content"`
}
func (f *CreateIssueForm) Name(field string) string {
names := map[string]string{
"IssueName": "Issue name",
}
return names[field]
}
func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
// \/ \/ \/ \/ \/
type CreateMilestoneForm struct {
Title string `form:"title" binding:"Required;MaxSize(50)"`
Content string `form:"content"`
Deadline string `form:"due_date"`
}
func (f *CreateMilestoneForm) Name(field string) string {
names := map[string]string{
"Title": "Milestone name",
}
return names[field]
}
func (f *CreateMilestoneForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
// .____ ___. .__
// | | _____ \_ |__ ____ | |
// | | \__ \ | __ \_/ __ \| |
// | |___ / __ \| \_\ \ ___/| |__
// |_______ (____ /___ /\___ >____/
// \/ \/ \/ \/
type CreateLabelForm struct {
Title string `form:"title" binding:"Required;MaxSize(50)"`
Color string `form:"color" binding:"Required;Size(7)"`
}
func (f *CreateLabelForm) Name(field string) string {
names := map[string]string{
"Title": "Label name",
"Color": "Label color",
}
return names[field]
}
func (f *CreateLabelForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
// __________ .__
// \______ \ ____ | | ____ _____ ______ ____
// | _// __ \| | _/ __ \\__ \ / ___// __ \
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
// |____|_ /\___ >____/\___ >____ /____ >\___ >
// \/ \/ \/ \/ \/ \/
type NewReleaseForm struct {
TagName string `form:"tag_name" binding:"Required"`
Target string `form:"tag_target" binding:"Required"`
Title string `form:"title" binding:"Required"`
Content string `form:"content" binding:"Required"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
}
func (f *NewReleaseForm) Name(field string) string {
names := map[string]string{
"TagName": "Tag name",
"Target": "Target",
"Title": "Release title",
"Content": "Release content",
}
return names[field]
}
func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
type EditReleaseForm struct {
Target string `form:"tag_target" binding:"Required"`
Title string `form:"title" binding:"Required"`
Content string `form:"content" binding:"Required"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
}
func (f *EditReleaseForm) Name(field string) string {
names := map[string]string{
"Target": "Target",
"Title": "Release title",
"Content": "Release content",
}
return names[field]
}
func (f *EditReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}

@ -0,0 +1,165 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
"github.com/gogits/gogs/modules/middleware/binding"
)
// _______________________________________ _________.______________________ _______________.___.
// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | |
// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | |
// | | \| \ | | / | \/ \| | | | / | \ | \\____ |
// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______|
// \/ \/ \/ \/ \/ \/ \/
type CreateRepoForm struct {
Uid int64 `form:"uid" binding:"Required"`
RepoName string `form:"repo_name" binding:"Required;AlphaDash;MaxSize(100)"`
Private bool `form:"private"`
Description string `form:"desc" binding:"MaxSize(255)"`
Gitignore string `form:"gitignore"`
License string `form:"license"`
InitReadme bool `form:"init_readme"`
}
func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
type MigrateRepoForm struct {
Url string `form:"url" binding:"Url"`
AuthUserName string `form:"auth_username"`
AuthPasswd string `form:"auth_password"`
Uid int64 `form:"uid" binding:"Required"`
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
Mirror bool `form:"mirror"`
Private bool `form:"private"`
Description string `form:"desc" binding:"MaxSize(255)"`
}
func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
type RepoSettingForm struct {
RepoName string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
Description string `form:"desc" binding:"MaxSize(255)"`
Website string `form:"site" binding:"Url;MaxSize(100)"`
Branch string `form:"branch"`
Interval int `form:"interval"`
Private bool `form:"private"`
GoGet bool `form:"goget"`
}
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
// __ __ ___. .__ .__ __
// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
// \/ \/ \/ \/ \/ \/
type NewWebhookForm struct {
Url string `form:"url" binding:"Required;Url"`
ContentType string `form:"content_type" binding:"Required"`
Secret string `form:"secret""`
PushOnly bool `form:"push_only"`
Active bool `form:"active"`
}
func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
// .___
// | | ______ ________ __ ____
// | |/ ___// ___/ | \_/ __ \
// | |\___ \ \___ \| | /\ ___/
// |___/____ >____ >____/ \___ >
// \/ \/ \/
type CreateIssueForm struct {
IssueName string `form:"title" binding:"Required;MaxSize(50)"`
MilestoneId int64 `form:"milestoneid"`
AssigneeId int64 `form:"assigneeid"`
Labels string `form:"labels"`
Content string `form:"content"`
}
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
// \/ \/ \/ \/ \/
type CreateMilestoneForm struct {
Title string `form:"title" binding:"Required;MaxSize(50)"`
Content string `form:"content"`
Deadline string `form:"due_date"`
}
func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
// .____ ___. .__
// | | _____ \_ |__ ____ | |
// | | \__ \ | __ \_/ __ \| |
// | |___ / __ \| \_\ \ ___/| |__
// |_______ (____ /___ /\___ >____/
// \/ \/ \/ \/
type CreateLabelForm struct {
Title string `form:"title" binding:"Required;MaxSize(50)"`
Color string `form:"color" binding:"Required;Size(7)"`
}
func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
// __________ .__
// \______ \ ____ | | ____ _____ ______ ____
// | _// __ \| | _/ __ \\__ \ / ___// __ \
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
// |____|_ /\___ >____/\___ >____ /____ >\___ >
// \/ \/ \/ \/ \/ \/
type NewReleaseForm struct {
TagName string `form:"tag_name" binding:"Required"`
Target string `form:"tag_target" binding:"Required"`
Title string `form:"title" binding:"Required"`
Content string `form:"content" binding:"Required"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
}
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
type EditReleaseForm struct {
Target string `form:"tag_target" binding:"Required"`
Title string `form:"title" binding:"Required"`
Content string `form:"content" binding:"Required"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
}
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}

@ -1,299 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"net/http"
"reflect"
"strings"
"github.com/go-martini/martini"
"github.com/gogits/session"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware/binding"
"github.com/gogits/gogs/modules/setting"
)
// Web form interface.
type Form interface {
Name(field string) string
}
type RegisterForm struct {
UserName string `form:"username" binding:"Required;AlphaDashDot;MaxSize(30)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
RetypePasswd string `form:"retypepasswd"`
LoginType string `form:"logintype"`
LoginName string `form:"loginname"`
}
func (f *RegisterForm) Name(field string) string {
names := map[string]string{
"UserName": "Username",
"Email": "E-mail address",
"Password": "Password",
"RetypePasswd": "Re-type password",
}
return names[field]
}
func (f *RegisterForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errs, data, f)
}
type LogInForm struct {
UserName string `form:"username" binding:"Required;MaxSize(35)"`
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
Remember bool `form:"remember"`
}
func (f *LogInForm) Name(field string) string {
names := map[string]string{
"UserName": "Username",
"Password": "Password",
}
return names[field]
}
func (f *LogInForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errs, data, f)
}
func GetMinMaxSize(field reflect.StructField) string {
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
if strings.HasPrefix(rule, "MinSize(") || strings.HasPrefix(rule, "MaxSize(") {
return rule[8 : len(rule)-1]
}
}
return ""
}
func validate(errs *binding.Errors, data base.TmplData, f Form) {
if errs.Count() == 0 {
return
} else if len(errs.Overall) > 0 {
for _, err := range errs.Overall {
log.Error("%s: %v", reflect.TypeOf(f), err)
}
return
}
data["HasError"] = true
AssignForm(f, data)
typ := reflect.TypeOf(f)
val := reflect.ValueOf(f)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldName := field.Tag.Get("form")
// Allow ignored fields in the struct
if fieldName == "-" {
continue
}
if err, ok := errs.Fields[field.Name]; ok {
data["Err_"+field.Name] = true
switch err {
case binding.BindingRequireError:
data["ErrorMsg"] = f.Name(field.Name) + " cannot be empty"
case binding.BindingAlphaDashError:
data["ErrorMsg"] = f.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
case binding.BindingAlphaDashDotError:
data["ErrorMsg"] = f.Name(field.Name) + " must be valid alpha or numeric or dash(-_) or dot characters"
case binding.BindingMinSizeError:
data["ErrorMsg"] = f.Name(field.Name) + " must contain at least " + GetMinMaxSize(field) + " characters"
case binding.BindingMaxSizeError:
data["ErrorMsg"] = f.Name(field.Name) + " must contain at most " + GetMinMaxSize(field) + " characters"
case binding.BindingEmailError:
data["ErrorMsg"] = f.Name(field.Name) + " is not a valid e-mail address"
case binding.BindingUrlError:
data["ErrorMsg"] = f.Name(field.Name) + " is not a valid URL"
default:
data["ErrorMsg"] = "Unknown error: " + err
}
return
}
}
}
// AssignForm assign form values back to the template data.
func AssignForm(form interface{}, data base.TmplData) {
typ := reflect.TypeOf(form)
val := reflect.ValueOf(form)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldName := field.Tag.Get("form")
// Allow ignored fields in the struct
if fieldName == "-" {
continue
}
data[fieldName] = val.Field(i).Interface()
}
}
type InstallForm struct {
Database string `form:"database" binding:"Required"`
Host string `form:"host"`
User string `form:"user"`
Passwd string `form:"passwd"`
DatabaseName string `form:"database_name"`
SslMode string `form:"ssl_mode"`
DatabasePath string `form:"database_path"`
RepoRootPath string `form:"repo_path"`
RunUser string `form:"run_user"`
Domain string `form:"domain"`
AppUrl string `form:"app_url"`
AdminName string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(30)"`
AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
SmtpHost string `form:"smtp_host"`
SmtpEmail string `form:"mailer_user"`
SmtpPasswd string `form:"mailer_pwd"`
RegisterConfirm string `form:"register_confirm"`
MailNotify string `form:"mail_notify"`
}
func (f *InstallForm) Name(field string) string {
names := map[string]string{
"Database": "Database name",
"AdminName": "Admin user name",
"AdminPasswd": "Admin password",
"AdminEmail": "Admin e-maill address",
}
return names[field]
}
func (f *InstallForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
// SignedInId returns the id of signed in user.
func SignedInId(header http.Header, sess session.SessionStore) int64 {
if !models.HasEngine {
return 0
}
if setting.Service.EnableReverseProxyAuth {
webAuthUser := header.Get(setting.ReverseProxyAuthUser)
if len(webAuthUser) > 0 {
u, err := models.GetUserByName(webAuthUser)
if err != nil {
if err != models.ErrUserNotExist {
log.Error("auth.user.SignedInId(GetUserByName): %v", err)
}
return 0
}
return u.Id
}
}
uid := sess.Get("userId")
if uid == nil {
return 0
}
if id, ok := uid.(int64); ok {
if _, err := models.GetUserById(id); err != nil {
if err != models.ErrUserNotExist {
log.Error("auth.user.SignedInId(GetUserById): %v", err)
}
return 0
}
return id
}
return 0
}
// SignedInUser returns the user object of signed user.
func SignedInUser(header http.Header, sess session.SessionStore) *models.User {
uid := SignedInId(header, sess)
if uid <= 0 {
return nil
}
u, err := models.GetUserById(uid)
if err != nil {
log.Error("user.SignedInUser: %v", err)
return nil
}
return u
}
// IsSignedIn check if any user has signed in.
func IsSignedIn(header http.Header, sess session.SessionStore) bool {
return SignedInId(header, sess) > 0
}
type FeedsForm struct {
UserId int64 `form:"userid" binding:"Required"`
Page int64 `form:"p"`
}
type UpdateProfileForm struct {
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
FullName string `form:"fullname" binding:"MaxSize(40)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Website string `form:"website" binding:"Url;MaxSize(50)"`
Location string `form:"location" binding:"MaxSize(50)"`
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
}
func (f *UpdateProfileForm) Name(field string) string {
names := map[string]string{
"UserName": "Username",
"Email": "E-mail address",
"Website": "Website address",
"Location": "Location",
"Avatar": "Gravatar Email",
}
return names[field]
}
func (f *UpdateProfileForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errs, data, f)
}
type UpdatePasswdForm struct {
OldPasswd string `form:"oldpasswd" binding:"Required;MinSize(6);MaxSize(30)"`
NewPasswd string `form:"newpasswd" binding:"Required;MinSize(6);MaxSize(30)"`
RetypePasswd string `form:"retypepasswd"`
}
func (f *UpdatePasswdForm) Name(field string) string {
names := map[string]string{
"OldPasswd": "Old password",
"NewPasswd": "New password",
"RetypePasswd": "Re-type password",
}
return names[field]
}
func (f *UpdatePasswdForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errs, data, f)
}

@ -0,0 +1,98 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
"github.com/gogits/gogs/modules/middleware/binding"
)
type InstallForm struct {
Database string `form:"database" binding:"Required"`
Host string `form:"host"`
User string `form:"user"`
Passwd string `form:"passwd"`
DatabaseName string `form:"database_name"`
SslMode string `form:"ssl_mode"`
DatabasePath string `form:"database_path"`
RepoRootPath string `form:"repo_path"`
RunUser string `form:"run_user"`
Domain string `form:"domain"`
AppUrl string `form:"app_url"`
AdminName string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(30)"`
AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
SmtpHost string `form:"smtp_host"`
SmtpEmail string `form:"mailer_user"`
SmtpPasswd string `form:"mailer_pwd"`
RegisterConfirm string `form:"register_confirm"`
MailNotify string `form:"mail_notify"`
}
func (f *InstallForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
// _____ ____ _________________ ___
// / _ \ | | \__ ___/ | \
// / /_\ \| | / | | / ~ \
// / | \ | / | | \ Y /
// \____|__ /______/ |____| \___|_ /
// \/ \/
type RegisterForm struct {
UserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Password string `form:"password" binding:"Required;MinSize(6);MaxSize(30)"`
Retype string `form:"retype"`
LoginType string `form:"logintype"`
LoginName string `form:"loginname"`
}
func (f *RegisterForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
type SignInForm struct {
UserName string `form:"uname" binding:"Required;MaxSize(35)"`
Password string `form:"password" binding:"Required;MinSize(6);MaxSize(30)"`
Remember bool `form:"remember"`
}
func (f *SignInForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
// __________________________________________.___ _______ ________ _________
// / _____/\_ _____/\__ ___/\__ ___/| |\ \ / _____/ / _____/
// \_____ \ | __)_ | | | | | |/ | \/ \ ___ \_____ \
// / \ | \ | | | | | / | \ \_\ \/ \
// /_______ //_______ / |____| |____| |___\____|__ /\______ /_______ /
// \/ \/ \/ \/ \/
type UpdateProfileForm struct {
UserName string `form:"uname" binding:"Required;MaxSize(35)"`
FullName string `form:"fullname" binding:"MaxSize(40)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Website string `form:"website" binding:"Url;MaxSize(50)"`
Location string `form:"location" binding:"MaxSize(50)"`
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
}
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}
type ChangePasswordForm struct {
OldPassword string `form:"old_password" binding:"Required;MinSize(6);MaxSize(30)"`
Password string `form:"password" binding:"Required;MinSize(6);MaxSize(30)"`
Retype string `form:"retype"`
}
func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
validate(errs, ctx.Data, f, l)
}

@ -5,9 +5,7 @@
package base package base
type ( type (
// Type TmplData represents data in the templates. TplName string
TmplData map[string]interface{}
TplName string
ApiJsonErr struct { ApiJsonErr struct {
Message string `json:"message"` Message string `json:"message"`

@ -51,7 +51,7 @@ var mailDomains = map[string]string{
var TemplateFuncs template.FuncMap = map[string]interface{}{ var TemplateFuncs template.FuncMap = map[string]interface{}{
"GoVer": func() string { "GoVer": func() string {
return runtime.Version() return strings.Title(runtime.Version())
}, },
"AppName": func() string { "AppName": func() string {
return setting.AppName return setting.AppName
@ -69,7 +69,8 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
}, },
"AvatarLink": AvatarLink, "AvatarLink": AvatarLink,
"str2html": Str2html, "str2html": Str2html, // TODO: Legacy
"Str2html": Str2html,
"TimeSince": TimeSince, "TimeSince": TimeSince,
"FileSize": FileSize, "FileSize": FileSize,
"Subtract": Subtract, "Subtract": Subtract,
@ -98,8 +99,11 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DiffTypeToStr": DiffTypeToStr, "DiffTypeToStr": DiffTypeToStr,
"DiffLineTypeToStr": DiffLineTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr,
"ShortSha": ShortSha, "ShortSha": ShortSha,
"Oauth2Icon": Oauth2Icon, "Md5": EncodeMd5,
"Oauth2Name": Oauth2Name, "ActionContent2Commits": ActionContent2Commits,
"Oauth2Icon": Oauth2Icon,
"Oauth2Name": Oauth2Name,
"CreateCaptcha": func() string { return "" },
} }
type Actioner interface { type Actioner interface {
@ -117,11 +121,11 @@ type Actioner interface {
func ActionIcon(opType int) string { func ActionIcon(opType int) string {
switch opType { switch opType {
case 1: // Create repository. case 1: // Create repository.
return "plus-circle" return "repo"
case 5, 9: // Commit repository. case 5, 9: // Commit repository.
return "arrow-circle-o-right" return "git-commit"
case 6: // Create issue. case 6: // Create issue.
return "exclamation-circle" return "issue-opened"
case 8: // Transfer repository. case 8: // Transfer repository.
return "share" return "share"
case 10: // Comment issue. case 10: // Comment issue.
@ -131,6 +135,7 @@ func ActionIcon(opType int) string {
} }
} }
// TODO: Legacy
const ( const (
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>` TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s` TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
@ -155,6 +160,15 @@ type PushCommits struct {
Commits []*PushCommit Commits []*PushCommit
} }
func ActionContent2Commits(act Actioner) *PushCommits {
var push *PushCommits
if err := json.Unmarshal([]byte(act.GetContent()), &push); err != nil {
return nil
}
return push
}
// TODO: Legacy
// ActionDesc accepts int that represents action operation type // ActionDesc accepts int that represents action operation type
// and returns the description. // and returns the description.
func ActionDesc(act Actioner) string { func ActionDesc(act Actioner) string {

@ -14,10 +14,13 @@ import (
"hash" "hash"
"html/template" "html/template"
"math" "math"
"strconv" r "math/rand"
"strings" "strings"
"time" "time"
"github.com/Unknwon/com"
"github.com/Unknwon/i18n"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
@ -43,6 +46,33 @@ func GetRandomString(n int, alphabets ...byte) string {
return string(bytes) return string(bytes)
} }
// RandomCreateBytes generate random []byte by specify chars.
func RandomCreateBytes(n int, alphabets ...byte) []byte {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
var randby bool
if num, err := rand.Read(bytes); num != n || err != nil {
r.Seed(time.Now().UnixNano())
randby = true
}
for i, b := range bytes {
if len(alphabets) == 0 {
if randby {
bytes[i] = alphanum[r.Intn(len(alphanum))]
} else {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
} else {
if randby {
bytes[i] = alphabets[r.Intn(len(alphabets))]
} else {
bytes[i] = alphabets[b%byte(len(alphabets))]
}
}
}
return bytes
}
// http://code.google.com/p/go/source/browse/pbkdf2/pbkdf2.go?repo=crypto // http://code.google.com/p/go/source/browse/pbkdf2/pbkdf2.go?repo=crypto
func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
prf := hmac.New(h, password) prf := hmac.New(h, password)
@ -90,7 +120,7 @@ func VerifyTimeLimitCode(data string, minutes int, code string) bool {
// split code // split code
start := code[:12] start := code[:12]
lives := code[12:18] lives := code[12:18]
if d, err := StrTo(lives).Int(); err == nil { if d, err := com.StrTo(lives).Int(); err == nil {
minutes = d minutes = d
} }
@ -134,7 +164,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
// create sha1 encode string // create sha1 encode string
sh := sha1.New() sh := sha1.New()
sh.Write([]byte(data + setting.SecretKey + startStr + endStr + ToStr(minutes))) sh.Write([]byte(data + setting.SecretKey + startStr + endStr + com.ToStr(minutes)))
encoded := hex.EncodeToString(sh.Sum(nil)) encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
@ -240,60 +270,59 @@ func TimeSincePro(then time.Time) string {
return strings.TrimPrefix(timeStr, ", ") return strings.TrimPrefix(timeStr, ", ")
} }
// timeSince calculates the time interval and generate user-friendly string. func timeSince(then time.Time, lang string) string {
func timeSince(then time.Time) string {
now := time.Now() now := time.Now()
lbl := "ago" lbl := i18n.Tr(lang, "tool.ago")
diff := now.Unix() - then.Unix() diff := now.Unix() - then.Unix()
if then.After(now) { if then.After(now) {
lbl = "from now" lbl = i18n.Tr(lang, "tool.from_now")
diff = then.Unix() - now.Unix() diff = then.Unix() - now.Unix()
} }
switch { switch {
case diff <= 0: case diff <= 0:
return "now" return i18n.Tr(lang, "tool.now")
case diff <= 2: case diff <= 2:
return fmt.Sprintf("1 second %s", lbl) return i18n.Tr(lang, "tool.1s", lbl)
case diff < 1*Minute: case diff < 1*Minute:
return fmt.Sprintf("%d seconds %s", diff, lbl) return i18n.Tr(lang, "tool.seconds", diff, lbl)
case diff < 2*Minute: case diff < 2*Minute:
return fmt.Sprintf("1 minute %s", lbl) return i18n.Tr(lang, "tool.1m", lbl)
case diff < 1*Hour: case diff < 1*Hour:
return fmt.Sprintf("%d minutes %s", diff/Minute, lbl) return i18n.Tr(lang, "tool.minutes", diff/Minute, lbl)
case diff < 2*Hour: case diff < 2*Hour:
return fmt.Sprintf("1 hour %s", lbl) return i18n.Tr(lang, "tool.1h", lbl)
case diff < 1*Day: case diff < 1*Day:
return fmt.Sprintf("%d hours %s", diff/Hour, lbl) return i18n.Tr(lang, "tool.hours", diff/Hour, lbl)
case diff < 2*Day: case diff < 2*Day:
return fmt.Sprintf("1 day %s", lbl) return i18n.Tr(lang, "tool.1d", lbl)
case diff < 1*Week: case diff < 1*Week:
return fmt.Sprintf("%d days %s", diff/Day, lbl) return i18n.Tr(lang, "tool.days", diff/Day, lbl)
case diff < 2*Week: case diff < 2*Week:
return fmt.Sprintf("1 week %s", lbl) return i18n.Tr(lang, "tool.1w", lbl)
case diff < 1*Month: case diff < 1*Month:
return fmt.Sprintf("%d weeks %s", diff/Week, lbl) return i18n.Tr(lang, "tool.weeks", diff/Week, lbl)
case diff < 2*Month: case diff < 2*Month:
return fmt.Sprintf("1 month %s", lbl) return i18n.Tr(lang, "tool.1mon", lbl)
case diff < 1*Year: case diff < 1*Year:
return fmt.Sprintf("%d months %s", diff/Month, lbl) return i18n.Tr(lang, "tool.months", diff/Month, lbl)
case diff < 2*Year: case diff < 2*Year:
return fmt.Sprintf("1 year %s", lbl) return i18n.Tr(lang, "tool.1y", lbl)
default: default:
return fmt.Sprintf("%d years %s", diff/Year, lbl) return i18n.Tr(lang, "tool.years", diff/Year, lbl)
} }
} }
// TimeSince calculates the time interval and generate user-friendly string. // TimeSince calculates the time interval and generate user-friendly string.
func TimeSince(t time.Time) template.HTML { func TimeSince(t time.Time, lang string) template.HTML {
return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`, t.Format(setting.TimeFormat), timeSince(t))) return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`, t.Format(setting.TimeFormat), timeSince(t, lang)))
} }
const ( const (
@ -445,88 +474,3 @@ func DateFormat(t time.Time, format string) string {
format = replacer.Replace(format) format = replacer.Replace(format)
return t.Format(format) return t.Format(format)
} }
// convert string to specify type
type StrTo string
func (f StrTo) Exist() bool {
return string(f) != string(0x1E)
}
func (f StrTo) Int() (int, error) {
v, err := strconv.ParseInt(f.String(), 10, 32)
return int(v), err
}
func (f StrTo) Int64() (int64, error) {
v, err := strconv.ParseInt(f.String(), 10, 64)
return int64(v), err
}
func (f StrTo) MustInt() int {
v, _ := f.Int()
return v
}
func (f StrTo) MustInt64() int64 {
v, _ := f.Int64()
return v
}
func (f StrTo) String() string {
if f.Exist() {
return string(f)
}
return ""
}
// convert any type to string
func ToStr(value interface{}, args ...int) (s string) {
switch v := value.(type) {
case bool:
s = strconv.FormatBool(v)
case float32:
s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
case float64:
s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
case int:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int8:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int16:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int32:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int64:
s = strconv.FormatInt(v, argInt(args).Get(0, 10))
case uint:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint8:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint16:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint32:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint64:
s = strconv.FormatUint(v, argInt(args).Get(0, 10))
case string:
s = v
case []byte:
s = string(v)
default:
s = fmt.Sprintf("%v", v)
}
return s
}
type argInt []int
func (a argInt) Get(i int, args ...int) (r int) {
if i >= 0 && i < len(a) {
r = a[i]
} else if len(args) > 0 {
r = args[0]
}
return
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,201 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package captcha a middleware that provides captcha service for Macaron.
package captcha
import (
"fmt"
"html/template"
"net/http"
"path"
"strings"
"github.com/Unknwon/macaron"
"github.com/gogits/cache"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
var (
defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
)
const (
// default captcha attributes
challengeNums = 6
expiration = 600
fieldIdName = "captcha_id"
fieldCaptchaName = "captcha"
cachePrefix = "captcha_"
defaultURLPrefix = "/captcha/"
)
// Captcha struct
type Captcha struct {
store cache.Cache
// url prefix for captcha image
URLPrefix string
// specify captcha id input field name
FieldIdName string
// specify captcha result input field name
FieldCaptchaName string
// captcha image width and height
StdWidth int
StdHeight int
// captcha chars nums
ChallengeNums int
// captcha expiration seconds
Expiration int64
// cache key prefix
CachePrefix string
}
// generate key string
func (c *Captcha) key(id string) string {
return c.CachePrefix + id
}
// generate rand chars with default chars
func (c *Captcha) genRandChars() []byte {
return base.RandomCreateBytes(c.ChallengeNums, defaultChars...)
}
// beego filter handler for serve captcha image
func (c *Captcha) Handler(ctx *macaron.Context) {
var chars []byte
id := path.Base(ctx.Req.RequestURI)
if i := strings.Index(id, "."); i != -1 {
id = id[:i]
}
key := c.key(id)
if v, ok := c.store.Get(key).([]byte); ok {
chars = v
} else {
ctx.Status(404)
ctx.Write([]byte("captcha not found"))
return
}
// reload captcha
if len(ctx.Query("reload")) > 0 {
chars = c.genRandChars()
if err := c.store.Put(key, chars, c.Expiration); err != nil {
ctx.Status(500)
ctx.Write([]byte("captcha reload error"))
log.Error(4, "Reload Create Captcha Error: %v", err)
return
}
}
img := NewImage(chars, c.StdWidth, c.StdHeight)
if _, err := img.WriteTo(ctx.RW()); err != nil {
log.Error(4, "Write Captcha Image Error: %v", err)
}
}
// tempalte func for output html
func (c *Captcha) CreateCaptchaHtml() template.HTML {
value, err := c.CreateCaptcha()
if err != nil {
log.Error(4, "Create Captcha Error: %v", err)
return ""
}
// create html
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`+
`<a class="captcha" href="javascript:">`+
`<img onclick="this.src=('%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s.png">`+
`</a>`, c.FieldIdName, value, c.URLPrefix, value, c.URLPrefix, value))
}
// create a new captcha id
func (c *Captcha) CreateCaptcha() (string, error) {
// generate captcha id
id := string(base.RandomCreateBytes(15))
// get the captcha chars
chars := c.genRandChars()
// save to store
if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil {
return "", err
}
return id, nil
}
// verify from a request
func (c *Captcha) VerifyReq(req *http.Request) bool {
req.ParseForm()
return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
}
// direct verify id and challenge string
func (c *Captcha) Verify(id string, challenge string) (success bool) {
if len(challenge) == 0 || len(id) == 0 {
return
}
var chars []byte
key := c.key(id)
if v, ok := c.store.Get(key).([]byte); ok && len(v) == len(challenge) {
chars = v
} else {
return
}
defer func() {
// finally remove it
c.store.Delete(key)
}()
// verify challenge
for i, c := range chars {
if c != challenge[i]-48 {
return
}
}
return true
}
// create a new captcha.Captcha
func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha {
cpt := &Captcha{}
cpt.store = store
cpt.FieldIdName = fieldIdName
cpt.FieldCaptchaName = fieldCaptchaName
cpt.ChallengeNums = challengeNums
cpt.Expiration = expiration
cpt.CachePrefix = cachePrefix
cpt.StdWidth = stdWidth
cpt.StdHeight = stdHeight
if len(urlPrefix) == 0 {
urlPrefix = defaultURLPrefix
}
if urlPrefix[len(urlPrefix)-1] != '/' {
urlPrefix += "/"
}
cpt.URLPrefix = urlPrefix
base.TemplateFuncs["CreateCaptcha"] = cpt.CreateCaptchaHtml
return cpt
}

@ -0,0 +1,487 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import (
"bytes"
"image"
"image/color"
"image/png"
"io"
"math"
)
const (
fontWidth = 11
fontHeight = 18
blackChar = 1
// Standard width and height of a captcha image.
stdWidth = 240
stdHeight = 80
// Maximum absolute skew factor of a single digit.
maxSkew = 0.7
// Number of background circles.
circleCount = 20
)
var font = [][]byte{
{ // 0
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 1
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{ // 2
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{ // 3
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 4
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
},
{ // 5
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 6
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 7
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
},
{ // 8
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 9
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
},
}
type Image struct {
*image.Paletted
numWidth int
numHeight int
dotSize int
}
var prng = &siprng{}
// randIntn returns a pseudorandom non-negative int in range [0, n).
func randIntn(n int) int {
return prng.Intn(n)
}
// randInt returns a pseudorandom int in range [from, to].
func randInt(from, to int) int {
return prng.Intn(to+1-from) + from
}
// randFloat returns a pseudorandom float64 in range [from, to].
func randFloat(from, to float64) float64 {
return (to-from)*prng.Float64() + from
}
func randomPalette() color.Palette {
p := make([]color.Color, circleCount+1)
// Transparent color.
p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
// Primary color.
prim := color.RGBA{
uint8(randIntn(129)),
uint8(randIntn(129)),
uint8(randIntn(129)),
0xFF,
}
p[1] = prim
// Circle colors.
for i := 2; i <= circleCount; i++ {
p[i] = randomBrightness(prim, 255)
}
return p
}
// NewImage returns a new captcha image of the given width and height with the
// given digits, where each digit must be in range 0-9.
func NewImage(digits []byte, width, height int) *Image {
m := new(Image)
m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette())
m.calculateSizes(width, height, len(digits))
// Randomly position captcha inside the image.
maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
maxy := height - m.numHeight - m.dotSize*2
var border int
if width > height {
border = height / 5
} else {
border = width / 5
}
x := randInt(border, maxx-border)
y := randInt(border, maxy-border)
// Draw digits.
for _, n := range digits {
m.drawDigit(font[n], x, y)
x += m.numWidth + m.dotSize
}
// Draw strike-through line.
m.strikeThrough()
// Apply wave distortion.
m.distort(randFloat(5, 10), randFloat(100, 200))
// Fill image with random circles.
m.fillWithCircles(circleCount, m.dotSize)
return m
}
// encodedPNG encodes an image to PNG and returns
// the result as a byte slice.
func (m *Image) encodedPNG() []byte {
var buf bytes.Buffer
if err := png.Encode(&buf, m.Paletted); err != nil {
panic(err.Error())
}
return buf.Bytes()
}
// WriteTo writes captcha image in PNG format into the given writer.
func (m *Image) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(m.encodedPNG())
return int64(n), err
}
func (m *Image) calculateSizes(width, height, ncount int) {
// Goal: fit all digits inside the image.
var border int
if width > height {
border = height / 4
} else {
border = width / 4
}
// Convert everything to floats for calculations.
w := float64(width - border*2)
h := float64(height - border*2)
// fw takes into account 1-dot spacing between digits.
fw := float64(fontWidth + 1)
fh := float64(fontHeight)
nc := float64(ncount)
// Calculate the width of a single digit taking into account only the
// width of the image.
nw := w / nc
// Calculate the height of a digit from this width.
nh := nw * fh / fw
// Digit too high?
if nh > h {
// Fit digits based on height.
nh = h
nw = fw / fh * nh
}
// Calculate dot size.
m.dotSize = int(nh / fh)
// Save everything, making the actual width smaller by 1 dot to account
// for spacing between digits.
m.numWidth = int(nw) - m.dotSize
m.numHeight = int(nh)
}
func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
for x := fromX; x <= toX; x++ {
m.SetColorIndex(x, y, colorIdx)
}
}
func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
f := 1 - radius
dfx := 1
dfy := -2 * radius
xo := 0
yo := radius
m.SetColorIndex(x, y+radius, colorIdx)
m.SetColorIndex(x, y-radius, colorIdx)
m.drawHorizLine(x-radius, x+radius, y, colorIdx)
for xo < yo {
if f >= 0 {
yo--
dfy += 2
f += dfy
}
xo++
dfx += 2
f += dfx
m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
}
}
func (m *Image) fillWithCircles(n, maxradius int) {
maxx := m.Bounds().Max.X
maxy := m.Bounds().Max.Y
for i := 0; i < n; i++ {
colorIdx := uint8(randInt(1, circleCount-1))
r := randInt(1, maxradius)
m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
}
}
func (m *Image) strikeThrough() {
maxx := m.Bounds().Max.X
maxy := m.Bounds().Max.Y
y := randInt(maxy/3, maxy-maxy/3)
amplitude := randFloat(5, 20)
period := randFloat(80, 180)
dx := 2.0 * math.Pi / period
for x := 0; x < maxx; x++ {
xo := amplitude * math.Cos(float64(y)*dx)
yo := amplitude * math.Sin(float64(x)*dx)
for yn := 0; yn < m.dotSize; yn++ {
r := randInt(0, m.dotSize)
m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
}
}
}
func (m *Image) drawDigit(digit []byte, x, y int) {
skf := randFloat(-maxSkew, maxSkew)
xs := float64(x)
r := m.dotSize / 2
y += randInt(-r, r)
for yo := 0; yo < fontHeight; yo++ {
for xo := 0; xo < fontWidth; xo++ {
if digit[yo*fontWidth+xo] != blackChar {
continue
}
m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
}
xs += skf
x = int(xs)
}
}
func (m *Image) distort(amplude float64, period float64) {
w := m.Bounds().Max.X
h := m.Bounds().Max.Y
oldm := m.Paletted
newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
dx := 2.0 * math.Pi / period
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
xo := amplude * math.Sin(float64(y)*dx)
yo := amplude * math.Cos(float64(x)*dx)
newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
}
}
m.Paletted = newm
}
func randomBrightness(c color.RGBA, max uint8) color.RGBA {
minc := min3(c.R, c.G, c.B)
maxc := max3(c.R, c.G, c.B)
if maxc > max {
return c
}
n := randIntn(int(max-maxc)) - int(minc)
return color.RGBA{
uint8(int(c.R) + n),
uint8(int(c.G) + n),
uint8(int(c.B) + n),
uint8(c.A),
}
}
func min3(x, y, z uint8) (m uint8) {
m = x
if y < m {
m = y
}
if z < m {
m = z
}
return
}
func max3(x, y, z uint8) (m uint8) {
m = x
if y > m {
m = y
}
if z > m {
m = z
}
return
}

@ -0,0 +1,42 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import (
"testing"
"github.com/gogits/gogs/modules/base"
)
type byteCounter struct {
n int64
}
func (bc *byteCounter) Write(b []byte) (int, error) {
bc.n += int64(len(b))
return len(b), nil
}
func BenchmarkNewImage(b *testing.B) {
b.StopTimer()
d := base.RandomCreateBytes(challengeNums, defaultChars...)
b.StartTimer()
for i := 0; i < b.N; i++ {
NewImage(d, stdWidth, stdHeight)
}
}
func BenchmarkImageWriteTo(b *testing.B) {
b.StopTimer()
d := base.RandomCreateBytes(challengeNums, defaultChars...)
b.StartTimer()
counter := &byteCounter{}
for i := 0; i < b.N; i++ {
img := NewImage(d, stdWidth, stdHeight)
img.WriteTo(counter)
b.SetBytes(counter.n)
counter.n = 0
}
}

@ -0,0 +1,267 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import (
"crypto/rand"
"encoding/binary"
"io"
"sync"
)
// siprng is PRNG based on SipHash-2-4.
type siprng struct {
mu sync.Mutex
k0, k1, ctr uint64
}
// siphash implements SipHash-2-4, accepting a uint64 as a message.
func siphash(k0, k1, m uint64) uint64 {
// Initialization.
v0 := k0 ^ 0x736f6d6570736575
v1 := k1 ^ 0x646f72616e646f6d
v2 := k0 ^ 0x6c7967656e657261
v3 := k1 ^ 0x7465646279746573
t := uint64(8) << 56
// Compression.
v3 ^= m
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= m
// Compress last block.
v3 ^= t
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= t
// Finalization.
v2 ^= 0xff
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
return v0 ^ v1 ^ v2 ^ v3
}
// rekey sets a new PRNG key, which is read from crypto/rand.
func (p *siprng) rekey() {
var k [16]byte
if _, err := io.ReadFull(rand.Reader, k[:]); err != nil {
panic(err.Error())
}
p.k0 = binary.LittleEndian.Uint64(k[0:8])
p.k1 = binary.LittleEndian.Uint64(k[8:16])
p.ctr = 1
}
// Uint64 returns a new pseudorandom uint64.
// It rekeys PRNG on the first call and every 64 MB of generated data.
func (p *siprng) Uint64() uint64 {
p.mu.Lock()
if p.ctr == 0 || p.ctr > 8*1024*1024 {
p.rekey()
}
v := siphash(p.k0, p.k1, p.ctr)
p.ctr++
p.mu.Unlock()
return v
}
func (p *siprng) Int63() int64 {
return int64(p.Uint64() & 0x7fffffffffffffff)
}
func (p *siprng) Uint32() uint32 {
return uint32(p.Uint64())
}
func (p *siprng) Int31() int32 {
return int32(p.Uint32() & 0x7fffffff)
}
func (p *siprng) Intn(n int) int {
if n <= 0 {
panic("invalid argument to Intn")
}
if n <= 1<<31-1 {
return int(p.Int31n(int32(n)))
}
return int(p.Int63n(int64(n)))
}
func (p *siprng) Int63n(n int64) int64 {
if n <= 0 {
panic("invalid argument to Int63n")
}
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
v := p.Int63()
for v > max {
v = p.Int63()
}
return v % n
}
func (p *siprng) Int31n(n int32) int32 {
if n <= 0 {
panic("invalid argument to Int31n")
}
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
v := p.Int31()
for v > max {
v = p.Int31()
}
return v % n
}
func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }

@ -0,0 +1,23 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import "testing"
func TestSiphash(t *testing.T) {
good := uint64(0xe849e8bb6ffe2567)
cur := siphash(0, 0, 0)
if cur != good {
t.Fatalf("siphash: expected %x, got %x", good, cur)
}
}
func BenchmarkSiprng(b *testing.B) {
b.SetBytes(8)
p := &siprng{}
for i := 0; i < b.N; i++ {
p.Uint64()
}
}

@ -0,0 +1,26 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"errors"
"io"
"github.com/Unknwon/com"
)
type Blob struct {
repo *Repository
*TreeEntry
}
func (b *Blob) Data() (io.Reader, error) {
stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.Id.String())
if err != nil {
return nil, errors.New(string(stderr))
}
return bytes.NewBuffer(stdout), nil
}

@ -0,0 +1,86 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"container/list"
"strings"
)
// Commit represents a git commit.
type Commit struct {
Tree
Id sha1 // The id of this commit object
Author *Signature
Committer *Signature
CommitMessage string
parents []sha1 // sha1 strings
}
// Return the commit message. Same as retrieving CommitMessage directly.
func (c *Commit) Message() string {
return c.CommitMessage
}
func (c *Commit) Summary() string {
return strings.Split(c.CommitMessage, "\n")[0]
}
// Return oid of the parent number n (0-based index). Return nil if no such parent exists.
func (c *Commit) ParentId(n int) (id sha1, err error) {
if n >= len(c.parents) {
err = IdNotExist
return
}
return c.parents[n], nil
}
// Return parent number n (0-based index)
func (c *Commit) Parent(n int) (*Commit, error) {
id, err := c.ParentId(n)
if err != nil {
return nil, err
}
parent, err := c.repo.getCommit(id)
if err != nil {
return nil, err
}
return parent, nil
}
// Return the number of parents of the commit. 0 if this is the
// root commit, otherwise 1,2,...
func (c *Commit) ParentCount() int {
return len(c.parents)
}
func (c *Commit) CommitsBefore() (*list.List, error) {
return c.repo.getCommitsBefore(c.Id)
}
func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
ec, err := c.repo.GetCommit(commitId)
if err != nil {
return nil, err
}
return c.repo.CommitsBetween(c, ec)
}
func (c *Commit) CommitsCount() (int, error) {
return c.repo.commitsCount(c.Id)
}
func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
return c.repo.searchCommits(c.Id, keyword)
}
func (c *Commit) CommitsByRange(page int) (*list.List, error) {
return c.repo.commitsByRange(c.Id, page)
}
func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
return c.repo.getCommitOfRelPath(c.Id, relPath)
}

@ -0,0 +1,36 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"fmt"
"github.com/Unknwon/com"
)
type ArchiveType int
const (
ZIP ArchiveType = iota + 1
TARGZ
)
func (c *Commit) CreateArchive(path string, archiveType ArchiveType) error {
var format string
switch archiveType {
case ZIP:
format = "zip"
case TARGZ:
format = "tar.gz"
default:
return fmt.Errorf("unknown format: %v", archiveType)
}
_, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.Id.String())
if err != nil {
return fmt.Errorf("%s", stderr)
}
return nil
}

@ -0,0 +1,27 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"path/filepath"
)
// Repository represents a Git repository.
type Repository struct {
Path string
commitCache map[sha1]*Commit
tagCache map[sha1]*Tag
}
// OpenRepository opens the repository at the given path.
func OpenRepository(repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err
}
return &Repository{Path: repoPath}, nil
}

@ -0,0 +1,38 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"errors"
"strings"
"github.com/Unknwon/com"
)
func IsBranchExist(repoPath, branchName string) bool {
_, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/heads/"+branchName)
return err == nil
}
func (repo *Repository) IsBranchExist(branchName string) bool {
return IsBranchExist(repo.Path, branchName)
}
func (repo *Repository) GetBranches() ([]string, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--heads")
if err != nil {
return nil, errors.New(stderr)
}
infos := strings.Split(stdout, "\n")
branches := make([]string, len(infos)-1)
for i, info := range infos[:len(infos)-1] {
parts := strings.Split(info, " ")
if len(parts) != 2 {
continue // NOTE: I should believe git will not give me wrong string.
}
branches[i] = strings.TrimPrefix(parts[1], "refs/heads/")
}
return branches, nil
}

@ -0,0 +1,291 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"container/list"
"errors"
"strings"
"sync"
"github.com/Unknwon/com"
)
func (repo *Repository) getCommitIdOfRef(refpath string) (string, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--verify", refpath)
if err != nil {
return "", errors.New(stderr)
}
return strings.Split(stdout, " ")[0], nil
}
func (repo *Repository) GetCommitIdOfBranch(branchName string) (string, error) {
return repo.getCommitIdOfRef("refs/heads/" + branchName)
}
// get branch's last commit or a special commit by id string
func (repo *Repository) GetCommitOfBranch(branchName string) (*Commit, error) {
commitId, err := repo.GetCommitIdOfBranch(branchName)
if err != nil {
return nil, err
}
return repo.GetCommit(commitId)
}
func (repo *Repository) GetCommitIdOfTag(tagName string) (string, error) {
return repo.getCommitIdOfRef("refs/tags/" + tagName)
}
func (repo *Repository) GetCommitOfTag(tagName string) (*Commit, error) {
commitId, err := repo.GetCommitIdOfTag(tagName)
if err != nil {
return nil, err
}
return repo.GetCommit(commitId)
}
// Parse commit information from the (uncompressed) raw
// data from the commit object.
// \n\n separate headers from message
func parseCommitData(data []byte) (*Commit, error) {
commit := new(Commit)
commit.parents = make([]sha1, 0, 1)
// we now have the contents of the commit object. Let's investigate...
nextline := 0
l:
for {
eol := bytes.IndexByte(data[nextline:], '\n')
switch {
case eol > 0:
line := data[nextline : nextline+eol]
spacepos := bytes.IndexByte(line, ' ')
reftype := line[:spacepos]
switch string(reftype) {
case "tree":
id, err := NewIdFromString(string(line[spacepos+1:]))
if err != nil {
return nil, err
}
commit.Tree.Id = id
case "parent":
// A commit can have one or more parents
oid, err := NewIdFromString(string(line[spacepos+1:]))
if err != nil {
return nil, err
}
commit.parents = append(commit.parents, oid)
case "author":
sig, err := newSignatureFromCommitline(line[spacepos+1:])
if err != nil {
return nil, err
}
commit.Author = sig
case "committer":
sig, err := newSignatureFromCommitline(line[spacepos+1:])
if err != nil {
return nil, err
}
commit.Committer = sig
}
nextline += eol + 1
case eol == 0:
commit.CommitMessage = string(data[nextline+1:])
break l
default:
break l
}
}
return commit, nil
}
func (repo *Repository) getCommit(id sha1) (*Commit, error) {
if repo.commitCache != nil {
if c, ok := repo.commitCache[id]; ok {
return c, nil
}
} else {
repo.commitCache = make(map[sha1]*Commit, 10)
}
data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
if err != nil {
return nil, errors.New(string(bytErr))
}
commit, err := parseCommitData(data)
if err != nil {
return nil, err
}
commit.repo = repo
commit.Id = id
repo.commitCache[id] = commit
return commit, nil
}
// Find the commit object in the repository.
func (repo *Repository) GetCommit(commitId string) (*Commit, error) {
id, err := NewIdFromString(commitId)
if err != nil {
return nil, err
}
return repo.getCommit(id)
}
func (repo *Repository) commitsCount(id sha1) (int, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", id.String())
if err != nil {
return 0, errors.New(stderr)
}
return com.StrTo(strings.TrimSpace(stdout)).Int()
}
// used only for single tree, (]
func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
l := list.New()
if last == nil || last.ParentCount() == 0 {
return l, nil
}
var err error
cur := last
for {
if cur.Id.Equal(before.Id) {
break
}
l.PushBack(cur)
if cur.ParentCount() == 0 {
break
}
cur, err = cur.Parent(0)
if err != nil {
return nil, err
}
}
return l, nil
}
func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *list.Element, id sha1, limit int) error {
commit, err := repo.getCommit(id)
if err != nil {
return err
}
var e *list.Element
if parent == nil {
e = l.PushBack(commit)
} else {
var in = parent
for {
if in == nil {
break
} else if in.Value.(*Commit).Id.Equal(commit.Id) {
return nil
} else {
if in.Next() == nil {
break
}
if in.Value.(*Commit).Committer.When.Equal(commit.Committer.When) {
break
}
if in.Value.(*Commit).Committer.When.After(commit.Committer.When) &&
in.Next().Value.(*Commit).Committer.When.Before(commit.Committer.When) {
break
}
}
in = in.Next()
}
e = l.InsertAfter(commit, in)
}
var pr = parent
if commit.ParentCount() > 1 {
pr = e
}
for i := 0; i < commit.ParentCount(); i++ {
id, err := commit.ParentId(i)
if err != nil {
return err
}
err = repo.commitsBefore(lock, l, pr, id, 0)
if err != nil {
return err
}
}
return nil
}
func (repo *Repository) CommitsCount(commitId string) (int, error) {
id, err := NewIdFromString(commitId)
if err != nil {
return 0, err
}
return repo.commitsCount(id)
}
func (repo *Repository) FileCommitsCount(branch, file string) (int, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
branch, "--", file)
if err != nil {
return 0, errors.New(stderr)
}
return com.StrTo(strings.TrimSpace(stdout)).Int()
}
func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*list.List, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", branch,
"--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat, "--", file)
if err != nil {
return nil, errors.New(string(stderr))
}
return parsePrettyFormatLog(repo, stdout)
}
func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
l := list.New()
lock := new(sync.Mutex)
err := repo.commitsBefore(lock, l, nil, id, 0)
return l, err
}
func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(), "-100",
"-i", "--grep="+keyword, prettyLogFormat)
if err != nil {
return nil, err
} else if len(stderr) > 0 {
return nil, errors.New(string(stderr))
}
return parsePrettyFormatLog(repo, stdout)
}
func (repo *Repository) commitsByRange(id sha1, page int) (*list.List, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(),
"--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat)
if err != nil {
return nil, errors.New(string(stderr))
}
return parsePrettyFormatLog(repo, stdout)
}
func (repo *Repository) getCommitOfRelPath(id sha1, relPath string) (*Commit, error) {
stdout, _, err := com.ExecCmdDir(repo.Path, "git", "log", "-1", prettyLogFormat, id.String(), "--", relPath)
if err != nil {
return nil, err
}
id, err = NewIdFromString(string(stdout))
if err != nil {
return nil, err
}
return repo.getCommit(id)
}

@ -2,9 +2,13 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package workers package git
// Work represents a background work interface of any kind. type ObjectType string
type Work interface {
Do() error const (
} COMMIT ObjectType = "commit"
TREE ObjectType = "tree"
BLOB ObjectType = "blob"
TAG ObjectType = "tag"
)

@ -0,0 +1,104 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"errors"
"strings"
"github.com/Unknwon/com"
)
func IsTagExist(repoPath, tagName string) bool {
_, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/tags/"+tagName)
return err == nil
}
func (repo *Repository) IsTagExist(tagName string) bool {
return IsTagExist(repo.Path, tagName)
}
// GetTags returns all tags of given repository.
func (repo *Repository) GetTags() ([]string, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", "-l")
if err != nil {
return nil, errors.New(stderr)
}
tags := strings.Split(stdout, "\n")
return tags[:len(tags)-1], nil
}
func (repo *Repository) CreateTag(tagName, idStr string) error {
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", tagName, idStr)
if err != nil {
return errors.New(stderr)
}
return nil
}
func (repo *Repository) getTag(id sha1) (*Tag, error) {
if repo.tagCache != nil {
if t, ok := repo.tagCache[id]; ok {
return t, nil
}
} else {
repo.tagCache = make(map[sha1]*Tag, 10)
}
// Get tag type.
tp, stderr, err := com.ExecCmdDir(repo.Path, "git", "cat-file", "-t", id.String())
if err != nil {
return nil, errors.New(stderr)
}
// Tag is a commit.
if ObjectType(tp) == COMMIT {
tag := &Tag{
Id: id,
Object: id,
Type: string(COMMIT),
repo: repo,
}
repo.tagCache[id] = tag
return tag, nil
}
// Tag with message.
data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
if err != nil {
return nil, errors.New(string(bytErr))
}
tag, err := parseTagData(data)
if err != nil {
return nil, err
}
tag.Id = id
tag.repo = repo
repo.tagCache[id] = tag
return tag, nil
}
// GetTag returns a Git tag by given name.
func (repo *Repository) GetTag(tagName string) (*Tag, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--tags", tagName)
if err != nil {
return nil, errors.New(stderr)
}
id, err := NewIdFromString(strings.Split(stdout, " ")[0])
if err != nil {
return nil, err
}
tag, err := repo.getTag(id)
if err != nil {
return nil, err
}
tag.Name = tagName
return tag, nil
}

@ -0,0 +1,32 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"fmt"
"github.com/Unknwon/com"
)
// Find the tree object in the repository.
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
id, err := NewIdFromString(idStr)
if err != nil {
return nil, err
}
return repo.getTree(id)
}
func (repo *Repository) getTree(id sha1) (*Tree, error) {
treePath := filepathFromSHA1(repo.Path, id.String())
if !com.IsFile(treePath) {
_, _, err := com.ExecCmdDir(repo.Path, "git", "ls-tree", id.String())
if err != nil {
return nil, fmt.Errorf("repo.getTree: %v", ErrNotExist)
}
}
return NewTree(repo, id), nil
}

@ -0,0 +1,87 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"encoding/hex"
"errors"
"fmt"
"strings"
)
var (
IdNotExist = errors.New("sha1 id not exist")
)
type sha1 [20]byte
// Return true if s has the same sha1 as caller.
// Support 40-length-string, []byte, sha1
func (id sha1) Equal(s2 interface{}) bool {
switch v := s2.(type) {
case string:
if len(v) != 40 {
return false
}
return v == id.String()
case []byte:
if len(v) != 20 {
return false
}
for i, v := range v {
if id[i] != v {
return false
}
}
case sha1:
for i, v := range v {
if id[i] != v {
return false
}
}
default:
return false
}
return true
}
// Return string (hex) representation of the Oid
func (s sha1) String() string {
result := make([]byte, 0, 40)
hexvalues := []byte("0123456789abcdef")
for i := 0; i < 20; i++ {
result = append(result, hexvalues[s[i]>>4])
result = append(result, hexvalues[s[i]&0xf])
}
return string(result)
}
// Create a new sha1 from a 20 byte slice.
func NewId(b []byte) (sha1, error) {
var id sha1
if len(b) != 20 {
return id, errors.New("Length must be 20")
}
for i := 0; i < 20; i++ {
id[i] = b[i]
}
return id, nil
}
// Create a new sha1 from a Sha1 string of length 40.
func NewIdFromString(s string) (sha1, error) {
s = strings.TrimSpace(s)
var id sha1
if len(s) != 40 {
return id, fmt.Errorf("Length must be 40")
}
b, err := hex.DecodeString(s)
if err != nil {
return id, err
}
return NewId(b)
}

@ -0,0 +1,40 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"strconv"
"time"
)
// Author and Committer information
type Signature struct {
Email string
Name string
When time.Time
}
// Helper to get a signature from the commit line, which looks like this:
// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
// but without the "author " at the beginning (this method should)
// be used for author and committer.
//
// FIXME: include timezone!
func newSignatureFromCommitline(line []byte) (*Signature, error) {
sig := new(Signature)
emailstart := bytes.IndexByte(line, '<')
sig.Name = string(line[:emailstart-1])
emailstop := bytes.IndexByte(line, '>')
sig.Email = string(line[emailstart+1 : emailstop])
timestop := bytes.IndexByte(line[emailstop+2:], ' ')
timestring := string(line[emailstop+2 : emailstop+2+timestop])
seconds, err := strconv.ParseInt(timestring, 10, 64)
if err != nil {
return nil, err
}
sig.When = time.Unix(seconds, 0)
return sig, nil
}

@ -0,0 +1,67 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
)
// Tag represents a Git tag.
type Tag struct {
Name string
Id sha1
repo *Repository
Object sha1 // The id of this commit object
Type string
Tagger *Signature
TagMessage string
}
func (tag *Tag) Commit() (*Commit, error) {
return tag.repo.getCommit(tag.Object)
}
// Parse commit information from the (uncompressed) raw
// data from the commit object.
// \n\n separate headers from message
func parseTagData(data []byte) (*Tag, error) {
tag := new(Tag)
// we now have the contents of the commit object. Let's investigate...
nextline := 0
l:
for {
eol := bytes.IndexByte(data[nextline:], '\n')
switch {
case eol > 0:
line := data[nextline : nextline+eol]
spacepos := bytes.IndexByte(line, ' ')
reftype := line[:spacepos]
switch string(reftype) {
case "object":
id, err := NewIdFromString(string(line[spacepos+1:]))
if err != nil {
return nil, err
}
tag.Object = id
case "type":
// A commit can have one or more parents
tag.Type = string(line[spacepos+1:])
case "tagger":
sig, err := newSignatureFromCommitline(line[spacepos+1:])
if err != nil {
return nil, err
}
tag.Tagger = sig
}
nextline += eol + 1
case eol == 0:
tag.TagMessage = string(data[nextline+1:])
break l
default:
break l
}
}
return tag, nil
}

@ -0,0 +1,124 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"errors"
"strings"
"github.com/Unknwon/com"
)
var (
ErrNotExist = errors.New("error not exist")
)
// A tree is a flat directory listing.
type Tree struct {
Id sha1
repo *Repository
// parent tree
ptree *Tree
entries Entries
entriesParsed bool
}
// Parse tree information from the (uncompressed) raw
// data from the tree object.
func parseTreeData(tree *Tree, data []byte) ([]*TreeEntry, error) {
entries := make([]*TreeEntry, 0, 10)
l := len(data)
pos := 0
for pos < l {
entry := new(TreeEntry)
entry.ptree = tree
step := 6
switch string(data[pos : pos+step]) {
case "100644":
entry.mode = ModeBlob
entry.Type = BLOB
case "100755":
entry.mode = ModeExec
entry.Type = BLOB
case "120000":
entry.mode = ModeSymlink
entry.Type = BLOB
case "160000":
entry.mode = ModeCommit
entry.Type = COMMIT
case "040000":
entry.mode = ModeTree
entry.Type = TREE
default:
return nil, errors.New("unknown type: " + string(data[pos:pos+step]))
}
pos += step + 6 // Skip string type of entry type.
step = 40
id, err := NewIdFromString(string(data[pos : pos+step]))
if err != nil {
return nil, err
}
entry.Id = id
pos += step + 1 // Skip half of sha1.
step = bytes.IndexByte(data[pos:], '\n')
entry.name = string(data[pos : pos+step])
pos += step + 1
entries = append(entries, entry)
}
return entries, nil
}
func (t *Tree) SubTree(rpath string) (*Tree, error) {
if len(rpath) == 0 {
return t, nil
}
paths := strings.Split(rpath, "/")
var err error
var g = t
var p = t
var te *TreeEntry
for _, name := range paths {
te, err = p.GetTreeEntryByPath(name)
if err != nil {
return nil, err
}
g, err = t.repo.getTree(te.Id)
if err != nil {
return nil, err
}
g.ptree = p
p = g
}
return g, nil
}
func (t *Tree) ListEntries(relpath string) (Entries, error) {
if t.entriesParsed {
return t.entries, nil
}
t.entriesParsed = true
stdout, _, err := com.ExecCmdDirBytes(t.repo.Path,
"git", "ls-tree", t.Id.String())
if err != nil {
return nil, err
}
t.entries, err = parseTreeData(t, stdout)
return t.entries, err
}
func NewTree(repo *Repository, id sha1) *Tree {
tree := new(Tree)
tree.Id = id
tree.repo = repo
return tree
}

@ -0,0 +1,59 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"fmt"
"path"
"strings"
)
func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
if len(relpath) == 0 {
return &TreeEntry{
Id: t.Id,
Type: TREE,
mode: ModeTree,
}, nil
// return nil, fmt.Errorf("GetTreeEntryByPath(empty relpath): %v", ErrNotExist)
}
relpath = path.Clean(relpath)
parts := strings.Split(relpath, "/")
var err error
tree := t
for i, name := range parts {
if i == len(parts)-1 {
entries, err := tree.ListEntries(path.Dir(relpath))
if err != nil {
return nil, err
}
for _, v := range entries {
if v.name == name {
return v, nil
}
}
} else {
tree, err = tree.SubTree(name)
if err != nil {
return nil, err
}
}
}
return nil, fmt.Errorf("GetTreeEntryByPath: %v", ErrNotExist)
}
func (t *Tree) GetBlobByPath(rpath string) (*Blob, error) {
entry, err := t.GetTreeEntryByPath(rpath)
if err != nil {
return nil, err
}
if !entry.IsDir() {
return entry.Blob(), nil
}
return nil, ErrNotExist
}

@ -0,0 +1,109 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"sort"
"strings"
"github.com/Unknwon/com"
)
type EntryMode int
// There are only a few file modes in Git. They look like unix file modes, but they can only be
// one of these.
const (
ModeBlob EntryMode = 0100644
ModeExec EntryMode = 0100755
ModeSymlink EntryMode = 0120000
ModeCommit EntryMode = 0160000
ModeTree EntryMode = 0040000
)
type TreeEntry struct {
Id sha1
Type ObjectType
mode EntryMode
name string
ptree *Tree
commited bool
size int64
sized bool
}
func (te *TreeEntry) Name() string {
return te.name
}
func (te *TreeEntry) Size() int64 {
if te.IsDir() {
return 0
}
if te.sized {
return te.size
}
stdout, _, err := com.ExecCmdDir(te.ptree.repo.Path, "git", "cat-file", "-s", te.Id.String())
if err != nil {
return 0
}
te.sized = true
te.size = com.StrTo(strings.TrimSpace(stdout)).MustInt64()
return te.size
}
func (te *TreeEntry) IsDir() bool {
return te.mode == ModeTree
}
func (te *TreeEntry) EntryMode() EntryMode {
return te.mode
}
func (te *TreeEntry) Blob() *Blob {
return &Blob{
repo: te.ptree.repo,
TreeEntry: te,
}
}
type Entries []*TreeEntry
var sorter = []func(t1, t2 *TreeEntry) bool{
func(t1, t2 *TreeEntry) bool {
return t1.IsDir() && !t2.IsDir()
},
func(t1, t2 *TreeEntry) bool {
return t1.name < t2.name
},
}
func (bs Entries) Len() int { return len(bs) }
func (bs Entries) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
func (bs Entries) Less(i, j int) bool {
t1, t2 := bs[i], bs[j]
var k int
for k = 0; k < len(sorter)-1; k++ {
sort := sorter[k]
switch {
case sort(t1, t2):
return true
case sort(t2, t1):
return false
}
}
return sorter[k](t1, t2)
}
func (bs Entries) Sort() {
sort.Sort(bs)
}

@ -0,0 +1,48 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"container/list"
"path/filepath"
"strings"
)
const prettyLogFormat = `--pretty=format:%H`
func parsePrettyFormatLog(repo *Repository, logByts []byte) (*list.List, error) {
l := list.New()
if len(logByts) == 0 {
return l, nil
}
parts := bytes.Split(logByts, []byte{'\n'})
for _, commitId := range parts {
commit, err := repo.GetCommit(string(commitId))
if err != nil {
return nil, err
}
l.PushBack(commit)
}
return l, nil
}
func RefEndName(refStr string) string {
index := strings.LastIndex(refStr, "/")
if index != -1 {
return refStr[index+1:]
}
return refStr
}
// If the object is stored in its own file (i.e not in a pack file),
// this function returns the full path to the object file.
// It does not test if the file exists.
func filepathFromSHA1(rootdir, sha1 string) string {
return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:])
}

@ -0,0 +1,43 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"errors"
"strings"
"github.com/Unknwon/com"
)
// Version represents version of Git.
type Version struct {
Major, Minor, Patch int
}
// GetVersion returns current Git version installed.
func GetVersion() (Version, error) {
stdout, stderr, err := com.ExecCmd("git", "version")
if err != nil {
return Version{}, errors.New(stderr)
}
infos := strings.Split(stdout, " ")
if len(infos) < 3 {
return Version{}, errors.New("not enough output")
}
v := Version{}
for i, s := range strings.Split(strings.TrimSpace(infos[2]), ".") {
switch i {
case 0:
v.Major, _ = com.StrTo(s).Int()
case 1:
v.Minor, _ = com.StrTo(s).Int()
case 2:
v.Patch, _ = com.StrTo(s).Int()
}
}
return v, nil
}

@ -0,0 +1,73 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package log
import (
"encoding/json"
"log"
"os"
"runtime"
)
type Brush func(string) string
func NewBrush(color string) Brush {
pre := "\033["
reset := "\033[0m"
return func(text string) string {
return pre + color + "m" + text + reset
}
}
var colors = []Brush{
NewBrush("1;36"), // Trace cyan
NewBrush("1;34"), // Debug blue
NewBrush("1;32"), // Info green
NewBrush("1;33"), // Warn yellow
NewBrush("1;31"), // Error red
NewBrush("1;35"), // Critical purple
NewBrush("1;31"), // Fatal red
}
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
type ConsoleWriter struct {
lg *log.Logger
Level int `json:"level"`
}
// create ConsoleWriter returning as LoggerInterface.
func NewConsole() LoggerInterface {
return &ConsoleWriter{
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
Level: TRACE,
}
}
func (cw *ConsoleWriter) Init(config string) error {
return json.Unmarshal([]byte(config), cw)
}
func (cw *ConsoleWriter) WriteMsg(msg string, skip, level int) error {
if cw.Level > level {
return nil
}
if runtime.GOOS == "windows" {
cw.lg.Println(msg)
} else {
cw.lg.Println(colors[level](msg))
}
return nil
}
func (_ *ConsoleWriter) Destroy() {
}
func (_ *ConsoleWriter) Flush() {
}
func init() {
Register("console", NewConsole)
}

@ -0,0 +1,237 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package log
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// FileLogWriter implements LoggerInterface.
// It writes messages by lines limit, file size limit, or time frequency.
type FileLogWriter struct {
*log.Logger
mw *MuxWriter
// The opened file
Filename string `json:"filename"`
Maxlines int `json:"maxlines"`
maxlines_curlines int
// Rotate at size
Maxsize int `json:"maxsize"`
maxsize_cursize int
// Rotate daily
Daily bool `json:"daily"`
Maxdays int64 `json:"maxdays`
daily_opendate int
Rotate bool `json:"rotate"`
startLock sync.Mutex // Only one log can write to the file
Level int `json:"level"`
}
// an *os.File writer with locker.
type MuxWriter struct {
sync.Mutex
fd *os.File
}
// write to os.File.
func (l *MuxWriter) Write(b []byte) (int, error) {
l.Lock()
defer l.Unlock()
return l.fd.Write(b)
}
// set os.File in writer.
func (l *MuxWriter) SetFd(fd *os.File) {
if l.fd != nil {
l.fd.Close()
}
l.fd = fd
}
// create a FileLogWriter returning as LoggerInterface.
func NewFileWriter() LoggerInterface {
w := &FileLogWriter{
Filename: "",
Maxlines: 1000000,
Maxsize: 1 << 28, //256 MB
Daily: true,
Maxdays: 7,
Rotate: true,
Level: TRACE,
}
// use MuxWriter instead direct use os.File for lock write when rotate
w.mw = new(MuxWriter)
// set MuxWriter as Logger's io.Writer
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
return w
}
// Init file logger with json config.
// config like:
// {
// "filename":"log/gogs.log",
// "maxlines":10000,
// "maxsize":1<<30,
// "daily":true,
// "maxdays":15,
// "rotate":true
// }
func (w *FileLogWriter) Init(config string) error {
if err := json.Unmarshal([]byte(config), w); err != nil {
return err
}
if len(w.Filename) == 0 {
return errors.New("config must have filename")
}
return w.StartLogger()
}
// start file logger. create log file and set to locker-inside file writer.
func (w *FileLogWriter) StartLogger() error {
fd, err := w.createLogFile()
if err != nil {
return err
}
w.mw.SetFd(fd)
if err = w.initFd(); err != nil {
return err
}
return nil
}
func (w *FileLogWriter) docheck(size int) {
w.startLock.Lock()
defer w.startLock.Unlock()
if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
(w.Daily && time.Now().Day() != w.daily_opendate)) {
if err := w.DoRotate(); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
return
}
}
w.maxlines_curlines++
w.maxsize_cursize += size
}
// write logger message into file.
func (w *FileLogWriter) WriteMsg(msg string, skip, level int) error {
if level < w.Level {
return nil
}
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
w.docheck(n)
w.Logger.Println(msg)
return nil
}
func (w *FileLogWriter) createLogFile() (*os.File, error) {
// Open the log file
return os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
}
func (w *FileLogWriter) initFd() error {
fd := w.mw.fd
finfo, err := fd.Stat()
if err != nil {
return fmt.Errorf("get stat: %s\n", err)
}
w.maxsize_cursize = int(finfo.Size())
w.daily_opendate = time.Now().Day()
if finfo.Size() > 0 {
content, err := ioutil.ReadFile(w.Filename)
if err != nil {
return err
}
w.maxlines_curlines = len(strings.Split(string(content), "\n"))
} else {
w.maxlines_curlines = 0
}
return nil
}
// DoRotate means it need to write file in new file.
// new file name like xx.log.2013-01-01.2
func (w *FileLogWriter) DoRotate() error {
_, err := os.Lstat(w.Filename)
if err == nil { // file exists
// Find the next available number
num := 1
fname := ""
for ; err == nil && num <= 999; num++ {
fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
_, err = os.Lstat(fname)
}
// return error if the last file checked still existed
if err == nil {
return fmt.Errorf("rotate: cannot find free log number to rename %s\n", w.Filename)
}
// block Logger's io.Writer
w.mw.Lock()
defer w.mw.Unlock()
fd := w.mw.fd
fd.Close()
// close fd before rename
// Rename the file to its newfound home
if err = os.Rename(w.Filename, fname); err != nil {
return fmt.Errorf("Rotate: %s\n", err)
}
// re-start logger
if err = w.StartLogger(); err != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", err)
}
go w.deleteOldLog()
}
return nil
}
func (w *FileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
os.Remove(path)
}
}
return nil
})
}
// destroy file logger, close file writer.
func (w *FileLogWriter) Destroy() {
w.mw.fd.Close()
}
// flush file logger.
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
func (w *FileLogWriter) Flush() {
w.mw.fd.Sync()
}
func init() {
Register("file", NewFileWriter)
}

@ -2,32 +2,29 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package log is a wrapper of logs for short calling name.
package log package log
import ( import (
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"github.com/gogits/logs" "runtime"
"strings"
"sync"
) )
var ( var (
loggers []*logs.BeeLogger loggers []*Logger
GitLogger *logs.BeeLogger GitLogger *Logger
) )
func init() {
NewLogger(0, "console", `{"level": 0}`)
}
func NewLogger(bufLen int64, mode, config string) { func NewLogger(bufLen int64, mode, config string) {
logger := logs.NewLogger(bufLen) logger := newLogger(bufLen)
isExist := false isExist := false
for _, l := range loggers { for _, l := range loggers {
if l.Adapter == mode { if l.adapter == mode {
isExist = true isExist = true
l = logger l = logger
} }
@ -35,15 +32,14 @@ func NewLogger(bufLen int64, mode, config string) {
if !isExist { if !isExist {
loggers = append(loggers, logger) loggers = append(loggers, logger)
} }
logger.SetLogFuncCallDepth(3)
if err := logger.SetLogger(mode, config); err != nil { if err := logger.SetLogger(mode, config); err != nil {
Fatal("Fail to set logger(%s): %v", mode, err) Fatal(1, "Fail to set logger(%s): %v", mode, err)
} }
} }
func NewGitLogger(logPath string) { func NewGitLogger(logPath string) {
os.MkdirAll(path.Dir(logPath), os.ModePerm) os.MkdirAll(path.Dir(logPath), os.ModePerm)
GitLogger = logs.NewLogger(0) GitLogger = newLogger(0)
GitLogger.SetLogger("file", fmt.Sprintf(`{"level":0,"filename":"%s","rotate":false}`, logPath)) GitLogger.SetLogger("file", fmt.Sprintf(`{"level":0,"filename":"%s","rotate":false}`, logPath))
} }
@ -65,28 +61,237 @@ func Info(format string, v ...interface{}) {
} }
} }
func Error(format string, v ...interface{}) { func Warn(format string, v ...interface{}) {
for _, logger := range loggers { for _, logger := range loggers {
logger.Error(format, v...) logger.Warn(format, v...)
} }
} }
func Warn(format string, v ...interface{}) { func Error(skip int, format string, v ...interface{}) {
for _, logger := range loggers { for _, logger := range loggers {
logger.Warn(format, v...) logger.Error(skip, format, v...)
} }
} }
func Critical(format string, v ...interface{}) { func Critical(skip int, format string, v ...interface{}) {
for _, logger := range loggers { for _, logger := range loggers {
logger.Critical(format, v...) logger.Critical(skip, format, v...)
} }
} }
func Fatal(format string, v ...interface{}) { func Fatal(skip int, format string, v ...interface{}) {
Error(format, v...) Error(skip, format, v...)
for _, l := range loggers { for _, l := range loggers {
l.Close() l.Close()
} }
os.Exit(2) os.Exit(1)
}
// .___ _______________________________________________________ _________ ___________
// | |\ \__ ___/\_ _____/\______ \_ _____/ _ \ \_ ___ \\_ _____/
// | |/ | \| | | __)_ | _/| __)/ /_\ \/ \ \/ | __)_
// | / | \ | | \ | | \| \/ | \ \____| \
// |___\____|__ /____| /_______ / |____|_ /\___ /\____|__ /\______ /_______ /
// \/ \/ \/ \/ \/ \/ \/
type LogLevel int
const (
TRACE = iota
DEBUG
INFO
WARN
ERROR
CRITICAL
FATAL
)
// LoggerInterface represents behaviors of a logger provider.
type LoggerInterface interface {
Init(config string) error
WriteMsg(msg string, skip, level int) error
Destroy()
Flush()
}
type loggerType func() LoggerInterface
var adapters = make(map[string]loggerType)
// Register registers given logger provider to adapters.
func Register(name string, log loggerType) {
if log == nil {
panic("log: register provider is nil")
}
if _, dup := adapters[name]; dup {
panic("log: register called twice for provider \"" + name + "\"")
}
adapters[name] = log
}
type logMsg struct {
skip, level int
msg string
}
// Logger is default logger in beego application.
// it can contain several providers and log message into all providers.
type Logger struct {
adapter string
lock sync.Mutex
level int
msg chan *logMsg
outputs map[string]LoggerInterface
quit chan bool
}
// newLogger initializes and returns a new logger.
func newLogger(buffer int64) *Logger {
l := &Logger{
msg: make(chan *logMsg, buffer),
outputs: make(map[string]LoggerInterface),
quit: make(chan bool),
}
go l.StartLogger()
return l
}
// SetLogger sets new logger instanse with given logger adapter and config.
func (l *Logger) SetLogger(adapter string, config string) error {
l.lock.Lock()
defer l.lock.Unlock()
if log, ok := adapters[adapter]; ok {
lg := log()
if err := lg.Init(config); err != nil {
return err
}
l.outputs[adapter] = lg
l.adapter = adapter
} else {
panic("log: unknown adapter \"" + adapter + "\" (forgotten Register?)")
}
return nil
}
// DelLogger removes a logger adapter instance.
func (l *Logger) DelLogger(adapter string) error {
l.lock.Lock()
defer l.lock.Unlock()
if lg, ok := l.outputs[adapter]; ok {
lg.Destroy()
delete(l.outputs, adapter)
} else {
panic("log: unknown adapter \"" + adapter + "\" (forgotten Register?)")
}
return nil
}
func (l *Logger) writerMsg(skip, level int, msg string) error {
if l.level > level {
return nil
}
lm := &logMsg{
skip: skip,
level: level,
}
// Only error information needs locate position for debugging.
if lm.level >= ERROR {
pc, file, line, ok := runtime.Caller(skip)
if ok {
// Get caller function name.
fn := runtime.FuncForPC(pc)
var fnName string
if fn == nil {
fnName = "?()"
} else {
fnName = strings.TrimLeft(filepath.Ext(fn.Name()), ".") + "()"
}
lm.msg = fmt.Sprintf("[%s:%d %s] %s", filepath.Base(file), line, fnName, msg)
} else {
lm.msg = msg
}
} else {
lm.msg = msg
}
l.msg <- lm
return nil
}
// StartLogger starts logger chan reading.
func (l *Logger) StartLogger() {
for {
select {
case bm := <-l.msg:
for _, l := range l.outputs {
l.WriteMsg(bm.msg, bm.skip, bm.level)
}
case <-l.quit:
return
}
}
}
// Flush flushs all chan data.
func (l *Logger) Flush() {
for _, l := range l.outputs {
l.Flush()
}
}
// Close closes logger, flush all chan data and destroy all adapter instances.
func (l *Logger) Close() {
l.quit <- true
for {
if len(l.msg) > 0 {
bm := <-l.msg
for _, l := range l.outputs {
l.WriteMsg(bm.msg, bm.skip, bm.level)
}
} else {
break
}
}
for _, l := range l.outputs {
l.Flush()
l.Destroy()
}
}
func (l *Logger) Trace(format string, v ...interface{}) {
msg := fmt.Sprintf("[T] "+format, v...)
l.writerMsg(0, TRACE, msg)
}
func (l *Logger) Debug(format string, v ...interface{}) {
msg := fmt.Sprintf("[D] "+format, v...)
l.writerMsg(0, DEBUG, msg)
}
func (l *Logger) Info(format string, v ...interface{}) {
msg := fmt.Sprintf("[I] "+format, v...)
l.writerMsg(0, INFO, msg)
}
func (l *Logger) Warn(format string, v ...interface{}) {
msg := fmt.Sprintf("[W] "+format, v...)
l.writerMsg(0, WARN, msg)
}
func (l *Logger) Error(skip int, format string, v ...interface{}) {
msg := fmt.Sprintf("[E] "+format, v...)
l.writerMsg(skip, ERROR, msg)
}
func (l *Logger) Critical(skip int, format string, v ...interface{}) {
msg := fmt.Sprintf("[C] "+format, v...)
l.writerMsg(skip, CRITICAL, msg)
}
func (l *Logger) Fatal(skip int, format string, v ...interface{}) {
msg := fmt.Sprintf("[F] "+format, v...)
l.writerMsg(skip, FATAL, msg)
l.Close()
os.Exit(1)
} }

@ -10,10 +10,12 @@ import (
"fmt" "fmt"
"path" "path"
"github.com/Unknwon/com"
"github.com/Unknwon/macaron"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
@ -55,7 +57,7 @@ func GetMailTmplData(u *models.User) map[interface{}]interface{} {
// create a time limit code for user active // create a time limit code for user active
func CreateUserActiveCode(u *models.User, startInf interface{}) string { func CreateUserActiveCode(u *models.User, startInf interface{}) string {
minutes := setting.Service.ActiveCodeLives minutes := setting.Service.ActiveCodeLives
data := base.ToStr(u.Id) + u.Email + u.LowerName + u.Passwd + u.Rands data := com.ToStr(u.Id) + u.Email + u.LowerName + u.Passwd + u.Rands
code := base.CreateTimeLimitCode(data, minutes, startInf) code := base.CreateTimeLimitCode(data, minutes, startInf)
// add tail hex username // add tail hex username
@ -64,7 +66,7 @@ func CreateUserActiveCode(u *models.User, startInf interface{}) string {
} }
// Send user register mail with active code // Send user register mail with active code
func SendRegisterMail(r *middleware.Render, u *models.User) { func SendRegisterMail(r macaron.Render, u *models.User) {
code := CreateUserActiveCode(u, nil) code := CreateUserActiveCode(u, nil)
subject := "Register success, Welcome" subject := "Register success, Welcome"
@ -72,7 +74,7 @@ func SendRegisterMail(r *middleware.Render, u *models.User) {
data["Code"] = code data["Code"] = code
body, err := r.HTMLString(string(AUTH_REGISTER_SUCCESS), data) body, err := r.HTMLString(string(AUTH_REGISTER_SUCCESS), data)
if err != nil { if err != nil {
log.Error("mail.SendRegisterMail(fail to render): %v", err) log.Error(4, "mail.SendRegisterMail(fail to render): %v", err)
return return
} }
@ -83,7 +85,7 @@ func SendRegisterMail(r *middleware.Render, u *models.User) {
} }
// Send email verify active email. // Send email verify active email.
func SendActiveMail(r *middleware.Render, u *models.User) { func SendActiveMail(r macaron.Render, u *models.User) {
code := CreateUserActiveCode(u, nil) code := CreateUserActiveCode(u, nil)
subject := "Verify your e-mail address" subject := "Verify your e-mail address"
@ -92,7 +94,7 @@ func SendActiveMail(r *middleware.Render, u *models.User) {
data["Code"] = code data["Code"] = code
body, err := r.HTMLString(string(AUTH_ACTIVE), data) body, err := r.HTMLString(string(AUTH_ACTIVE), data)
if err != nil { if err != nil {
log.Error("mail.SendActiveMail(fail to render): %v", err) log.Error(4, "mail.SendActiveMail(fail to render): %v", err)
return return
} }
@ -103,7 +105,7 @@ func SendActiveMail(r *middleware.Render, u *models.User) {
} }
// Send reset password email. // Send reset password email.
func SendResetPasswdMail(r *middleware.Render, u *models.User) { func SendResetPasswdMail(r macaron.Render, u *models.User) {
code := CreateUserActiveCode(u, nil) code := CreateUserActiveCode(u, nil)
subject := "Reset your password" subject := "Reset your password"
@ -112,7 +114,7 @@ func SendResetPasswdMail(r *middleware.Render, u *models.User) {
data["Code"] = code data["Code"] = code
body, err := r.HTMLString(string(AUTH_RESET_PASSWORD), data) body, err := r.HTMLString(string(AUTH_RESET_PASSWORD), data)
if err != nil { if err != nil {
log.Error("mail.SendResetPasswdMail(fail to render): %v", err) log.Error(4, "mail.SendResetPasswdMail(fail to render): %v", err)
return return
} }
@ -157,7 +159,7 @@ func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *
} }
// SendIssueMentionMail sends mail notification for who are mentioned in issue. // SendIssueMentionMail sends mail notification for who are mentioned in issue.
func SendIssueMentionMail(r *middleware.Render, u, owner *models.User, func SendIssueMentionMail(r macaron.Render, u, owner *models.User,
repo *models.Repository, issue *models.Issue, tos []string) error { repo *models.Repository, issue *models.Issue, tos []string) error {
if len(tos) == 0 { if len(tos) == 0 {
@ -182,7 +184,7 @@ func SendIssueMentionMail(r *middleware.Render, u, owner *models.User,
} }
// SendCollaboratorMail sends mail notification to new collaborator. // SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(r *middleware.Render, u, owner *models.User, func SendCollaboratorMail(r macaron.Render, u, owner *models.User,
repo *models.Repository) error { repo *models.Repository) error {
subject := fmt.Sprintf("%s added you to %s", owner.Name, repo.Name) subject := fmt.Sprintf("%s added you to %s", owner.Name, repo.Name)

@ -56,7 +56,7 @@ func processMailQueue() {
if len(msg.Info) > 0 { if len(msg.Info) > 0 {
info = ", info: " + msg.Info info = ", info: " + msg.Info
} }
log.Error(fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err)) log.Error(4, fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err))
} else { } else {
log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info)) log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info))
} }

@ -8,7 +8,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/go-martini/martini" "github.com/Unknwon/macaron"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
@ -20,7 +20,7 @@ type ToggleOptions struct {
DisableCsrf bool DisableCsrf bool
} }
func Toggle(options *ToggleOptions) martini.Handler { func Toggle(options *ToggleOptions) macaron.Handler {
return func(ctx *Context) { return func(ctx *Context) {
// Cannot view any page before installation. // Cannot view any page before installation.
if !setting.InstallLock { if !setting.InstallLock {
@ -49,7 +49,7 @@ func Toggle(options *ToggleOptions) martini.Handler {
ctx.Redirect("/user/login") ctx.Redirect("/user/login")
return return
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm { } else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = "Activate Your Account" // ctx.Data["Title"] = "Activate Your Account"
ctx.HTML(200, "user/activate") ctx.HTML(200, "user/activate")
return return
} }

@ -16,7 +16,8 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/go-martini/martini" "github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
) )
/* /*
@ -37,44 +38,44 @@ import (
// your own error handling, use Form or Json middleware directly. // your own error handling, use Form or Json middleware directly.
// An interface pointer can be added as a second argument in order // An interface pointer can be added as a second argument in order
// to map the struct to a specific interface. // to map the struct to a specific interface.
func Bind(obj interface{}, ifacePtr ...interface{}) martini.Handler { func Bind(obj interface{}, ifacePtr ...interface{}) macaron.Handler {
return func(context martini.Context, req *http.Request) { return func(ctx *macaron.Context) {
contentType := req.Header.Get("Content-Type") contentType := ctx.Req.Header.Get("Content-Type")
if strings.Contains(contentType, "form-urlencoded") { if strings.Contains(contentType, "form-urlencoded") {
context.Invoke(Form(obj, ifacePtr...)) ctx.Invoke(Form(obj, ifacePtr...))
} else if strings.Contains(contentType, "multipart/form-data") { } else if strings.Contains(contentType, "multipart/form-data") {
context.Invoke(MultipartForm(obj, ifacePtr...)) ctx.Invoke(MultipartForm(obj, ifacePtr...))
} else if strings.Contains(contentType, "json") { } else if strings.Contains(contentType, "json") {
context.Invoke(Json(obj, ifacePtr...)) ctx.Invoke(Json(obj, ifacePtr...))
} else { } else {
context.Invoke(Json(obj, ifacePtr...)) ctx.Invoke(Json(obj, ifacePtr...))
if getErrors(context).Count() > 0 { if getErrors(ctx).Count() > 0 {
context.Invoke(Form(obj, ifacePtr...)) ctx.Invoke(Form(obj, ifacePtr...))
} }
} }
context.Invoke(ErrorHandler) ctx.Invoke(ErrorHandler)
} }
} }
// BindIgnErr will do the exactly same thing as Bind but without any // BindIgnErr will do the exactly same thing as Bind but without any
// error handling, which user has freedom to deal with them. // error handling, which user has freedom to deal with them.
// This allows user take advantages of validation. // This allows user take advantages of validation.
func BindIgnErr(obj interface{}, ifacePtr ...interface{}) martini.Handler { func BindIgnErr(obj interface{}, ifacePtr ...interface{}) macaron.Handler {
return func(context martini.Context, req *http.Request) { return func(ctx *macaron.Context, req *http.Request) {
contentType := req.Header.Get("Content-Type") contentType := req.Header.Get("Content-Type")
if strings.Contains(contentType, "form-urlencoded") { if strings.Contains(contentType, "form-urlencoded") {
context.Invoke(Form(obj, ifacePtr...)) ctx.Invoke(Form(obj, ifacePtr...))
} else if strings.Contains(contentType, "multipart/form-data") { } else if strings.Contains(contentType, "multipart/form-data") {
context.Invoke(MultipartForm(obj, ifacePtr...)) ctx.Invoke(MultipartForm(obj, ifacePtr...))
} else if strings.Contains(contentType, "json") { } else if strings.Contains(contentType, "json") {
context.Invoke(Json(obj, ifacePtr...)) ctx.Invoke(Json(obj, ifacePtr...))
} else { } else {
context.Invoke(Json(obj, ifacePtr...)) ctx.Invoke(Json(obj, ifacePtr...))
if getErrors(context).Count() > 0 { if getErrors(ctx).Count() > 0 {
context.Invoke(Form(obj, ifacePtr...)) ctx.Invoke(Form(obj, ifacePtr...))
} }
} }
} }
@ -89,12 +90,12 @@ func BindIgnErr(obj interface{}, ifacePtr ...interface{}) martini.Handler {
// keys, for example: key=val1&key=val2&key=val3 // keys, for example: key=val1&key=val2&key=val3
// An interface pointer can be added as a second argument in order // An interface pointer can be added as a second argument in order
// to map the struct to a specific interface. // to map the struct to a specific interface.
func Form(formStruct interface{}, ifacePtr ...interface{}) martini.Handler { func Form(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
return func(context martini.Context, req *http.Request) { return func(ctx *macaron.Context) {
ensureNotPointer(formStruct) ensureNotPointer(formStruct)
formStruct := reflect.New(reflect.TypeOf(formStruct)) formStruct := reflect.New(reflect.TypeOf(formStruct))
errors := newErrors() errors := newErrors()
parseErr := req.ParseForm() parseErr := ctx.Req.ParseForm()
// Format validation of the request body or the URL would add considerable overhead, // Format validation of the request body or the URL would add considerable overhead,
// and ParseForm does not complain when URL encoding is off. // and ParseForm does not complain when URL encoding is off.
@ -104,14 +105,14 @@ func Form(formStruct interface{}, ifacePtr ...interface{}) martini.Handler {
errors.Overall[BindingDeserializationError] = parseErr.Error() errors.Overall[BindingDeserializationError] = parseErr.Error()
} }
mapForm(formStruct, req.Form, errors) mapForm(formStruct, ctx.Req.Form, errors)
validateAndMap(formStruct, context, errors, ifacePtr...) validateAndMap(formStruct, ctx, errors, ifacePtr...)
} }
} }
func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Handler { func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
return func(context martini.Context, req *http.Request) { return func(ctx *macaron.Context) {
ensureNotPointer(formStruct) ensureNotPointer(formStruct)
formStruct := reflect.New(reflect.TypeOf(formStruct)) formStruct := reflect.New(reflect.TypeOf(formStruct))
errors := newErrors() errors := newErrors()
@ -119,7 +120,7 @@ func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Hand
// Workaround for multipart forms returning nil instead of an error // Workaround for multipart forms returning nil instead of an error
// when content is not multipart // when content is not multipart
// https://code.google.com/p/go/issues/detail?id=6334 // https://code.google.com/p/go/issues/detail?id=6334
multipartReader, err := req.MultipartReader() multipartReader, err := ctx.Req.MultipartReader()
if err != nil { if err != nil {
errors.Overall[BindingDeserializationError] = err.Error() errors.Overall[BindingDeserializationError] = err.Error()
} else { } else {
@ -129,12 +130,12 @@ func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Hand
errors.Overall[BindingDeserializationError] = parseErr.Error() errors.Overall[BindingDeserializationError] = parseErr.Error()
} }
req.MultipartForm = form ctx.Req.MultipartForm = form
} }
mapForm(formStruct, req.MultipartForm.Value, errors) mapForm(formStruct, ctx.Req.MultipartForm.Value, errors)
validateAndMap(formStruct, context, errors, ifacePtr...) validateAndMap(formStruct, ctx, errors, ifacePtr...)
} }
} }
@ -143,21 +144,21 @@ func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Hand
// validated, but no error handling is actually performed here. // validated, but no error handling is actually performed here.
// An interface pointer can be added as a second argument in order // An interface pointer can be added as a second argument in order
// to map the struct to a specific interface. // to map the struct to a specific interface.
func Json(jsonStruct interface{}, ifacePtr ...interface{}) martini.Handler { func Json(jsonStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
return func(context martini.Context, req *http.Request) { return func(ctx *macaron.Context) {
ensureNotPointer(jsonStruct) ensureNotPointer(jsonStruct)
jsonStruct := reflect.New(reflect.TypeOf(jsonStruct)) jsonStruct := reflect.New(reflect.TypeOf(jsonStruct))
errors := newErrors() errors := newErrors()
if req.Body != nil { if ctx.Req.Body != nil {
defer req.Body.Close() defer ctx.Req.Body.Close()
} }
if err := json.NewDecoder(req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF { if err := json.NewDecoder(ctx.Req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF {
errors.Overall[BindingDeserializationError] = err.Error() errors.Overall[BindingDeserializationError] = err.Error()
} }
validateAndMap(jsonStruct, context, errors, ifacePtr...) validateAndMap(jsonStruct, ctx, errors, ifacePtr...)
} }
} }
@ -165,15 +166,15 @@ func Json(jsonStruct interface{}, ifacePtr ...interface{}) martini.Handler {
// passed in is a Validator, then the user-defined Validate method // passed in is a Validator, then the user-defined Validate method
// is executed, and its errors are mapped to the context. This middleware // is executed, and its errors are mapped to the context. This middleware
// performs no error handling: it merely detects them and maps them. // performs no error handling: it merely detects them and maps them.
func Validate(obj interface{}) martini.Handler { func Validate(obj interface{}) macaron.Handler {
return func(context martini.Context, req *http.Request) { return func(ctx *macaron.Context, l i18n.Locale) {
errors := newErrors() errors := newErrors()
validateStruct(errors, obj) validateStruct(errors, obj)
if validator, ok := obj.(Validator); ok { if validator, ok := obj.(Validator); ok {
validator.Validate(errors, req, context) validator.Validate(ctx, errors, l)
} }
context.Map(*errors) ctx.Map(*errors)
} }
} }
@ -387,9 +388,7 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
} }
} }
// Don't pass in pointers to bind to. Can lead to bugs. See: // Don't pass in pointers to bind to. Can lead to bugs.
// https://github.com/codegangsta/martini-contrib/issues/40
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
func ensureNotPointer(obj interface{}) { func ensureNotPointer(obj interface{}) {
if reflect.TypeOf(obj).Kind() == reflect.Ptr { if reflect.TypeOf(obj).Kind() == reflect.Ptr {
panic("Pointers are not accepted as binding models") panic("Pointers are not accepted as binding models")
@ -399,13 +398,13 @@ func ensureNotPointer(obj interface{}) {
// Performs validation and combines errors from validation // Performs validation and combines errors from validation
// with errors from deserialization, then maps both the // with errors from deserialization, then maps both the
// resulting struct and the errors to the context. // resulting struct and the errors to the context.
func validateAndMap(obj reflect.Value, context martini.Context, errors *Errors, ifacePtr ...interface{}) { func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors *Errors, ifacePtr ...interface{}) {
context.Invoke(Validate(obj.Interface())) ctx.Invoke(Validate(obj.Interface()))
errors.Combine(getErrors(context)) errors.Combine(getErrors(ctx))
context.Map(*errors) ctx.Map(*errors)
context.Map(obj.Elem().Interface()) ctx.Map(obj.Elem().Interface())
if len(ifacePtr) > 0 { if len(ifacePtr) > 0 {
context.MapTo(obj.Elem().Interface(), ifacePtr[0]) ctx.MapTo(obj.Elem().Interface(), ifacePtr[0])
} }
} }
@ -413,8 +412,8 @@ func newErrors() *Errors {
return &Errors{make(map[string]string), make(map[string]string)} return &Errors{make(map[string]string), make(map[string]string)}
} }
func getErrors(context martini.Context) Errors { func getErrors(ctx *macaron.Context) Errors {
return context.Get(reflect.TypeOf(Errors{})).Interface().(Errors) return ctx.GetVal(reflect.TypeOf(Errors{})).Interface().(Errors)
} }
type ( type (
@ -422,7 +421,7 @@ type (
// validation before the request even gets to your application. // validation before the request even gets to your application.
// The Validate method will be executed during the validation phase. // The Validate method will be executed during the validation phase.
Validator interface { Validator interface {
Validate(*Errors, *http.Request, martini.Context) Validate(*macaron.Context, *Errors, i18n.Locale)
} }
) )

@ -5,42 +5,33 @@
package middleware package middleware
import ( import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
"net/http" "net/http"
"net/url" "path"
"path/filepath"
"strconv"
"strings" "strings"
"time" "time"
"github.com/go-martini/martini" "github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
"github.com/gogits/cache" "github.com/macaron-contrib/session"
"github.com/gogits/git"
"github.com/gogits/session"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
// Context represents context of a request. // Context represents context of a request.
type Context struct { type Context struct {
*Render *macaron.Context
c martini.Context i18n.Locale
p martini.Params Flash *session.Flash
Req *http.Request Session session.Store
Res http.ResponseWriter
Flash *Flash
Session session.SessionStore
Cache cache.Cache
User *models.User User *models.User
IsSigned bool IsSigned bool
@ -68,7 +59,8 @@ type Context struct {
HTTPS string HTTPS string
Git string Git string
} }
Mirror *models.Mirror CommitsCount int
Mirror *models.Mirror
} }
} }
@ -107,12 +99,12 @@ func (ctx *Context) HasError() bool {
} }
// HTML calls render.HTML underlying but reduce one argument. // HTML calls render.HTML underlying but reduce one argument.
func (ctx *Context) HTML(status int, name base.TplName, htmlOpt ...HTMLOptions) { func (ctx *Context) HTML(status int, name base.TplName) {
ctx.Render.HTML(status, string(name), ctx.Data, htmlOpt...) ctx.Render.HTML(status, string(name), ctx.Data)
} }
// RenderWithErr used for page has form validation but need to prompt error to users. // RenderWithErr used for page has form validation but need to prompt error to users.
func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form auth.Form) { func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) {
if form != nil { if form != nil {
auth.AssignForm(form, ctx.Data) auth.AssignForm(form, ctx.Data)
} }
@ -124,8 +116,8 @@ func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form auth.Form)
// Handle handles and logs error by given status. // Handle handles and logs error by given status.
func (ctx *Context) Handle(status int, title string, err error) { func (ctx *Context) Handle(status int, title string, err error) {
if err != nil { if err != nil {
log.Error("%s: %v", title, err) log.Error(4, "%s: %v", title, err)
if martini.Dev != martini.Prod { if macaron.Env != macaron.PROD {
ctx.Data["ErrorMsg"] = err ctx.Data["ErrorMsg"] = err
} }
} }
@ -139,106 +131,6 @@ func (ctx *Context) Handle(status int, title string, err error) {
ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status))) ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status)))
} }
func (ctx *Context) GetCookie(name string) string {
cookie, err := ctx.Req.Cookie(name)
if err != nil {
return ""
}
return cookie.Value
}
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
cookie := http.Cookie{}
cookie.Name = name
cookie.Value = value
if len(others) > 0 {
switch v := others[0].(type) {
case int:
cookie.MaxAge = v
case int64:
cookie.MaxAge = int(v)
case int32:
cookie.MaxAge = int(v)
}
}
// default "/"
if len(others) > 1 {
if v, ok := others[1].(string); ok && len(v) > 0 {
cookie.Path = v
}
} else {
cookie.Path = "/"
}
// default empty
if len(others) > 2 {
if v, ok := others[2].(string); ok && len(v) > 0 {
cookie.Domain = v
}
}
// default empty
if len(others) > 3 {
switch v := others[3].(type) {
case bool:
cookie.Secure = v
default:
if others[3] != nil {
cookie.Secure = true
}
}
}
// default false. for session cookie default true
if len(others) > 4 {
if v, ok := others[4].(bool); ok && v {
cookie.HttpOnly = true
}
}
ctx.Res.Header().Add("Set-Cookie", cookie.String())
}
// Get secure cookie from request by a given key.
func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
val := ctx.GetCookie(key)
if val == "" {
return "", false
}
parts := strings.SplitN(val, "|", 3)
if len(parts) != 3 {
return "", false
}
vs := parts[0]
timestamp := parts[1]
sig := parts[2]
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
return "", false
}
res, _ := base64.URLEncoding.DecodeString(vs)
return string(res), true
}
// Set Secure cookie for response.
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
vs := base64.URLEncoding.EncodeToString([]byte(value))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
sig := fmt.Sprintf("%02x", h.Sum(nil))
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
ctx.SetCookie(name, cookie, others...)
}
func (ctx *Context) CsrfToken() string { func (ctx *Context) CsrfToken() string {
if len(ctx.csrfToken) > 0 { if len(ctx.csrfToken) > 0 {
return ctx.csrfToken return ctx.csrfToken
@ -271,16 +163,16 @@ func (ctx *Context) ServeFile(file string, names ...string) {
if len(names) > 0 { if len(names) > 0 {
name = names[0] name = names[0]
} else { } else {
name = filepath.Base(file) name = path.Base(file)
} }
ctx.Res.Header().Set("Content-Description", "File Transfer") ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Res.Header().Set("Content-Type", "application/octet-stream") ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+name) ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
ctx.Res.Header().Set("Content-Transfer-Encoding", "binary") ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Res.Header().Set("Expires", "0") ctx.Resp.Header().Set("Expires", "0")
ctx.Res.Header().Set("Cache-Control", "must-revalidate") ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Res.Header().Set("Pragma", "public") ctx.Resp.Header().Set("Pragma", "public")
http.ServeFile(ctx.Res, ctx.Req, file) http.ServeFile(ctx.Resp, ctx.Req, file)
} }
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) { func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
@ -291,87 +183,52 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
modtime = v modtime = v
} }
} }
ctx.Res.Header().Set("Content-Description", "File Transfer") ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Res.Header().Set("Content-Type", "application/octet-stream") ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+name) ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
ctx.Res.Header().Set("Content-Transfer-Encoding", "binary") ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Res.Header().Set("Expires", "0") ctx.Resp.Header().Set("Expires", "0")
ctx.Res.Header().Set("Cache-Control", "must-revalidate") ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Res.Header().Set("Pragma", "public") ctx.Resp.Header().Set("Pragma", "public")
http.ServeContent(ctx.Res, ctx.Req, name, modtime, r) http.ServeContent(ctx.Resp, ctx.Req, name, modtime, r)
}
type Flash struct {
url.Values
ErrorMsg, SuccessMsg string
}
func (f *Flash) Error(msg string) {
f.Set("error", msg)
f.ErrorMsg = msg
} }
func (f *Flash) Success(msg string) { // Contexter initializes a classic context for a request.
f.Set("success", msg) func Contexter() macaron.Handler {
f.SuccessMsg = msg return func(c *macaron.Context, l i18n.Locale, sess session.Store, f *session.Flash) {
}
// InitContext initializes a classic context for a request.
func InitContext() martini.Handler {
return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) {
ctx := &Context{ ctx := &Context{
c: c, Context: c,
// p: p, Locale: l,
Req: r, Flash: f,
Res: res, Session: sess,
Cache: setting.Cache,
Render: rd,
} }
ctx.Data["PageStartTime"] = time.Now() // Cache: setting.Cache,
// start session
ctx.Session = setting.SessionManager.SessionStart(res, r)
// Get flash. // Compute current URL for real-time change language.
values, err := url.ParseQuery(ctx.GetCookie("gogs_flash")) link := ctx.Req.RequestURI
if err != nil { i := strings.Index(link, "?")
log.Error("InitContext.ParseQuery(flash): %v", err) if i > -1 {
} else if len(values) > 0 { link = link[:i]
ctx.Flash = &Flash{Values: values}
ctx.Flash.ErrorMsg = ctx.Flash.Get("error")
ctx.Flash.SuccessMsg = ctx.Flash.Get("success")
ctx.Data["Flash"] = ctx.Flash
ctx.SetCookie("gogs_flash", "", -1)
} }
ctx.Flash = &Flash{Values: url.Values{}} ctx.Data["Link"] = link
rw := res.(martini.ResponseWriter) ctx.Data["PageStartTime"] = time.Now()
rw.Before(func(martini.ResponseWriter) {
ctx.Session.SessionRelease(res)
if flash := ctx.Flash.Encode(); len(flash) > 0 {
ctx.SetCookie("gogs_flash", flash, 0)
}
})
// Get user from session if logined. // Get user from session if logined.
user := auth.SignedInUser(ctx.req.Header, ctx.Session) ctx.User = auth.SignedInUser(ctx.Req.Header, ctx.Session)
ctx.User = user if ctx.User != nil {
ctx.IsSigned = user != nil ctx.IsSigned = true
ctx.Data["IsSigned"] = ctx.IsSigned
ctx.Data["IsSigned"] = ctx.IsSigned ctx.Data["SignedUser"] = ctx.User
ctx.Data["SignedUserId"] = ctx.User.Id
if user != nil { ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["SignedUser"] = user
ctx.Data["SignedUserId"] = user.Id
ctx.Data["SignedUserName"] = user.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin ctx.Data["IsAdmin"] = ctx.User.IsAdmin
} }
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") { if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err = ctx.Req.ParseMultipartForm(setting.AttachmentMaxSize << 20); err != nil { // 32MB max size if err := ctx.Req.ParseMultipartForm(setting.AttachmentMaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
ctx.Handle(500, "issue.Comment(ctx.Req.ParseMultipartForm)", err) ctx.Handle(500, "ParseMultipartForm", err)
return return
} }
} }
@ -381,7 +238,5 @@ func InitContext() martini.Handler {
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`) ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`)
c.Map(ctx) c.Map(ctx)
c.Next()
} }
} }

@ -1,52 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package middleware
import (
"fmt"
"log"
"net/http"
"runtime"
"time"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/setting"
)
var isWindows bool
func init() {
isWindows = runtime.GOOS == "windows"
}
func Logger() martini.Handler {
return func(res http.ResponseWriter, req *http.Request, ctx martini.Context, log *log.Logger) {
if setting.DisableRouterLog {
return
}
start := time.Now()
log.Printf("Started %s %s", req.Method, req.URL.Path)
rw := res.(martini.ResponseWriter)
ctx.Next()
content := fmt.Sprintf("Completed %v %s in %v", rw.Status(), http.StatusText(rw.Status()), time.Since(start))
if !isWindows {
switch rw.Status() {
case 200:
content = fmt.Sprintf("\033[1;32m%s\033[0m", content)
case 304:
content = fmt.Sprintf("\033[1;33m%s\033[0m", content)
case 404:
content = fmt.Sprintf("\033[1;31m%s\033[0m", content)
case 500:
content = fmt.Sprintf("\033[1;36m%s\033[0m", content)
}
}
log.Println(content)
}
}

@ -1,281 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// foked from https://github.com/martini-contrib/render/blob/master/render.go
package middleware
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/base"
)
const (
ContentType = "Content-Type"
ContentLength = "Content-Length"
ContentJSON = "application/json"
ContentHTML = "text/html"
ContentXHTML = "application/xhtml+xml"
defaultCharset = "UTF-8"
)
var helperFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield called with no layout defined")
},
}
type Delims struct {
Left string
Right string
}
type RenderOptions struct {
Directory string
Layout string
Extensions []string
Funcs []template.FuncMap
Delims Delims
Charset string
IndentJSON bool
HTMLContentType string
}
type HTMLOptions struct {
Layout string
}
func Renderer(options ...RenderOptions) martini.Handler {
opt := prepareOptions(options)
cs := prepareCharset(opt.Charset)
t := compile(opt)
return func(res http.ResponseWriter, req *http.Request, c martini.Context) {
var tc *template.Template
if martini.Env == martini.Dev {
tc = compile(opt)
} else {
tc, _ = t.Clone()
}
rd := &Render{res, req, tc, opt, cs, base.TmplData{}, time.Time{}}
rd.Data["TmplLoadTimes"] = func() string {
if rd.startTime.IsZero() {
return ""
}
return fmt.Sprint(time.Since(rd.startTime).Nanoseconds()/1e6) + "ms"
}
c.Map(rd.Data)
c.Map(rd)
}
}
func prepareCharset(charset string) string {
if len(charset) != 0 {
return "; charset=" + charset
}
return "; charset=" + defaultCharset
}
func prepareOptions(options []RenderOptions) RenderOptions {
var opt RenderOptions
if len(options) > 0 {
opt = options[0]
}
if len(opt.Directory) == 0 {
opt.Directory = "templates"
}
if len(opt.Extensions) == 0 {
opt.Extensions = []string{".tmpl"}
}
if len(opt.HTMLContentType) == 0 {
opt.HTMLContentType = ContentHTML
}
return opt
}
func compile(options RenderOptions) *template.Template {
dir := options.Directory
t := template.New(dir)
t.Delims(options.Delims.Left, options.Delims.Right)
template.Must(t.Parse("Martini"))
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
r, err := filepath.Rel(dir, path)
if err != nil {
return err
}
ext := filepath.Ext(r)
for _, extension := range options.Extensions {
if ext == extension {
buf, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
name := (r[0 : len(r)-len(ext)])
tmpl := t.New(filepath.ToSlash(name))
for _, funcs := range options.Funcs {
tmpl = tmpl.Funcs(funcs)
}
template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
break
}
}
return nil
})
return t
}
type Render struct {
http.ResponseWriter
req *http.Request
t *template.Template
opt RenderOptions
compiledCharset string
Data base.TmplData
startTime time.Time
}
func (r *Render) JSON(status int, v interface{}) {
var result []byte
var err error
if r.opt.IndentJSON {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
http.Error(r, err.Error(), 500)
return
}
r.Header().Set(ContentType, ContentJSON+r.compiledCharset)
r.WriteHeader(status)
r.Write(result)
}
func (r *Render) JSONString(v interface{}) (string, error) {
var result []byte
var err error
if r.opt.IndentJSON {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
return "", err
}
return string(result), nil
}
func (r *Render) renderBytes(name string, binding interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
opt := r.prepareHTMLOptions(htmlOpt)
if len(opt.Layout) > 0 {
r.addYield(name, binding)
name = opt.Layout
}
out, err := r.execute(name, binding)
if err != nil {
return nil, err
}
return out, nil
}
func (r *Render) HTML(status int, name string, binding interface{}, htmlOpt ...HTMLOptions) {
r.startTime = time.Now()
out, err := r.renderBytes(name, binding, htmlOpt...)
if err != nil {
http.Error(r, err.Error(), http.StatusInternalServerError)
return
}
r.Header().Set(ContentType, r.opt.HTMLContentType+r.compiledCharset)
r.WriteHeader(status)
io.Copy(r, out)
}
func (r *Render) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOptions) (string, error) {
if out, err := r.renderBytes(name, binding, htmlOpt...); err != nil {
return "", err
} else {
return out.String(), nil
}
}
func (r *Render) Error(status int, message ...string) {
r.WriteHeader(status)
if len(message) > 0 {
r.Write([]byte(message[0]))
}
}
func (r *Render) Redirect(location string, status ...int) {
code := http.StatusFound
if len(status) == 1 {
code = status[0]
}
http.Redirect(r, r.req, location, code)
}
func (r *Render) Template() *template.Template {
return r.t
}
func (r *Render) execute(name string, binding interface{}) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
return buf, r.t.ExecuteTemplate(buf, name, binding)
}
func (r *Render) addYield(name string, binding interface{}) {
funcs := template.FuncMap{
"yield": func() (template.HTML, error) {
buf, err := r.execute(name, binding)
return template.HTML(buf.String()), err
},
}
r.t.Funcs(funcs)
}
func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
if len(htmlOpt) > 0 {
return htmlOpt[0]
}
return HTMLOptions{
Layout: r.opt.Layout,
}
}

@ -10,20 +10,19 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/go-martini/martini" "github.com/Unknwon/macaron"
"github.com/gogits/git"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
func RepoAssignment(redirect bool, args ...bool) martini.Handler { func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
return func(ctx *Context, params martini.Params) { return func(ctx *Context) {
// valid brachname // To valid brach name.
var validBranch bool var validBranch bool
// display bare quick start if it is a bare repo // To display bare quick start if it is a bare repo.
var displayBare bool var displayBare bool
if len(args) >= 1 { if len(args) >= 1 {
@ -35,51 +34,53 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
} }
var ( var (
user *models.User u *models.User
err error err error
) )
userName := params["username"] userName := ctx.Params(":username")
repoName := params["reponame"] repoName := ctx.Params(":reponame")
refName := params["branchname"] refName := ctx.Params(":branchname")
if len(refName) == 0 {
refName = ctx.Params(":path")
}
// TODO: need more advanced onwership and access level check.
// Collaborators who have write access can be seen as owners. // Collaborators who have write access can be seen as owners.
if ctx.IsSigned { if ctx.IsSigned {
ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE) ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE)
if err != nil { if err != nil {
ctx.Handle(500, "RepoAssignment(HasAccess)", err) ctx.Handle(500, "HasAccess", err)
return return
} }
ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName) ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
} }
if !ctx.Repo.IsTrueOwner { if !ctx.Repo.IsTrueOwner {
user, err = models.GetUserByName(userName) u, err = models.GetUserByName(userName)
if err != nil { if err != nil {
if err == models.ErrUserNotExist { if err == models.ErrUserNotExist {
ctx.Handle(404, "RepoAssignment(GetUserByName)", err) ctx.Handle(404, "GetUserByName", err)
return return
} else if redirect { } else if redirect {
ctx.Redirect("/") ctx.Redirect("/")
return return
} }
ctx.Handle(500, "RepoAssignment(GetUserByName)", err) ctx.Handle(500, "GetUserByName", err)
return return
} }
} else { } else {
user = ctx.User u = ctx.User
} }
if user == nil { if u == nil {
if redirect { if redirect {
ctx.Redirect("/") ctx.Redirect("/")
return return
} }
ctx.Handle(403, "RepoAssignment", errors.New("invliad user account for single repository")) ctx.Handle(404, "RepoAssignment", errors.New("invliad user account for single repository"))
return return
} }
ctx.Repo.Owner = user ctx.Repo.Owner = u
// Organization owner team members are true owners as well. // Organization owner team members are true owners as well.
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) { if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
@ -87,16 +88,19 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
} }
// get repository // get repository
repo, err := models.GetRepositoryByName(user.Id, repoName) repo, err := models.GetRepositoryByName(u.Id, repoName)
if err != nil { if err != nil {
if err == models.ErrRepoNotExist { if err == models.ErrRepoNotExist {
ctx.Handle(404, "RepoAssignment", err) ctx.Handle(404, "GetRepositoryByName", err)
return return
} else if redirect { } else if redirect {
ctx.Redirect("/") ctx.Redirect("/")
return return
} }
ctx.Handle(500, "RepoAssignment", err) ctx.Handle(500, "GetRepositoryByName", err)
return
} else if err = repo.GetOwner(); err != nil {
ctx.Handle(500, "GetOwner", err)
return return
} }
@ -108,16 +112,16 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
// Check access. // Check access.
if repo.IsPrivate && !ctx.Repo.IsOwner { if repo.IsPrivate && !ctx.Repo.IsOwner {
if ctx.User == nil { if ctx.User == nil {
ctx.Handle(404, "RepoAssignment(HasAccess)", nil) ctx.Handle(404, "HasAccess", nil)
return return
} }
hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE) hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
if err != nil { if err != nil {
ctx.Handle(500, "RepoAssignment(HasAccess)", err) ctx.Handle(500, "HasAccess", err)
return return
} else if !hasAccess { } else if !hasAccess {
ctx.Handle(404, "RepoAssignment(HasAccess)", nil) ctx.Handle(404, "HasAccess", nil)
return return
} }
} }
@ -127,7 +131,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
if repo.IsMirror { if repo.IsMirror {
ctx.Repo.Mirror, err = models.GetMirror(repo.Id) ctx.Repo.Mirror, err = models.GetMirror(repo.Id)
if err != nil { if err != nil {
ctx.Handle(500, "RepoAssignment(GetMirror)", err) ctx.Handle(500, "GetMirror", err)
return return
} }
ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
@ -144,34 +148,33 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
return return
} }
ctx.Repo.GitRepo = gitRepo ctx.Repo.GitRepo = gitRepo
ctx.Repo.RepoLink = "/" + user.Name + "/" + repo.Name ctx.Repo.RepoLink = "/" + u.Name + "/" + repo.Name
tags, err := ctx.Repo.GitRepo.GetTags() tags, err := ctx.Repo.GitRepo.GetTags()
if err != nil { if err != nil {
ctx.Handle(500, "RepoAssignment(GetTags))", err) ctx.Handle(500, "GetTags", err)
return return
} }
ctx.Repo.Repository.NumTags = len(tags) ctx.Repo.Repository.NumTags = len(tags)
ctx.Data["Title"] = user.Name + "/" + repo.Name ctx.Data["Title"] = u.Name + "/" + repo.Name
ctx.Data["Repository"] = repo ctx.Data["Repository"] = repo
ctx.Data["Owner"] = user ctx.Data["Owner"] = ctx.Repo.Repository.Owner
ctx.Data["RepoLink"] = ctx.Repo.RepoLink ctx.Data["RepoLink"] = ctx.Repo.RepoLink
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
ctx.Data["IsRepositoryTrueOwner"] = ctx.Repo.IsTrueOwner ctx.Data["IsRepositoryTrueOwner"] = ctx.Repo.IsTrueOwner
ctx.Data["BranchName"] = ""
if setting.SshPort != 22 { if setting.SshPort != 22 {
ctx.Repo.CloneLink.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", setting.RunUser, setting.Domain, user.LowerName, repo.LowerName) ctx.Repo.CloneLink.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", setting.RunUser, setting.Domain, u.LowerName, repo.LowerName)
} else { } else {
ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.Domain, user.LowerName, repo.LowerName) ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.Domain, u.LowerName, repo.LowerName)
} }
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, user.LowerName, repo.LowerName) ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, u.LowerName, repo.LowerName)
ctx.Data["CloneLink"] = ctx.Repo.CloneLink ctx.Data["CloneLink"] = ctx.Repo.CloneLink
if ctx.Repo.Repository.IsGoget { if ctx.Repo.Repository.IsGoget {
ctx.Data["GoGetLink"] = fmt.Sprintf("%s%s/%s", setting.AppUrl, user.LowerName, repo.LowerName) ctx.Data["GoGetLink"] = fmt.Sprintf("%s%s/%s", setting.AppUrl, u.LowerName, repo.LowerName)
ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", setting.Domain, user.LowerName, repo.LowerName) ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", setting.Domain, u.LowerName, repo.LowerName)
} }
// when repo is bare, not valid branch // when repo is bare, not valid branch
@ -211,7 +214,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
return return
} }
} else { } else {
ctx.Handle(404, "RepoAssignment invalid repo", nil) ctx.Handle(404, "RepoAssignment invalid repo", errors.New("branch or tag not exist"))
return return
} }
@ -222,7 +225,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
} else { } else {
brs, err := gitRepo.GetBranches() brs, err := gitRepo.GetBranches()
if err != nil { if err != nil {
ctx.Handle(500, "RepoAssignment(GetBranches))", err) ctx.Handle(500, "GetBranches", err)
return return
} }
refName = brs[0] refName = brs[0]
@ -233,6 +236,13 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
ctx.Data["IsBranch"] = ctx.Repo.IsBranch ctx.Data["IsBranch"] = ctx.Repo.IsBranch
ctx.Data["IsCommit"] = ctx.Repo.IsCommit ctx.Data["IsCommit"] = ctx.Repo.IsCommit
ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
if err != nil {
ctx.Handle(500, "CommitsCount", err)
return
}
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
} }
log.Debug("displayBare: %v; IsBare: %v", displayBare, ctx.Repo.Repository.IsBare) log.Debug("displayBare: %v; IsBare: %v", displayBare, ctx.Repo.Repository.IsBare)
@ -240,7 +250,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
// repo is bare and display enable // repo is bare and display enable
if displayBare && ctx.Repo.Repository.IsBare { if displayBare && ctx.Repo.Repository.IsBare {
log.Debug("Bare repository: %s", ctx.Repo.RepoLink) log.Debug("Bare repository: %s", ctx.Repo.RepoLink)
ctx.HTML(200, "repo/single_bare") ctx.HTML(200, "repo/bare")
return return
} }
@ -251,9 +261,10 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
ctx.Data["TagName"] = ctx.Repo.TagName ctx.Data["TagName"] = ctx.Repo.TagName
brs, err := ctx.Repo.GitRepo.GetBranches() brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil { if err != nil {
log.Error("RepoAssignment(GetBranches): %v", err) log.Error(4, "GetBranches: %v", err)
} }
ctx.Data["Branches"] = brs ctx.Data["Branches"] = brs
ctx.Data["BrancheCount"] = len(brs)
// If not branch selected, try default one. // If not branch selected, try default one.
// If default branch doesn't exists, fall back to some other branch. // If default branch doesn't exists, fall back to some other branch.
@ -267,11 +278,11 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
ctx.Data["BranchName"] = ctx.Repo.BranchName ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["CommitId"] = ctx.Repo.CommitId ctx.Data["CommitId"] = ctx.Repo.CommitId
ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching ctx.Data["IsWatchingRepo"] = ctx.Repo.IsWatching
} }
} }
func RequireTrueOwner() martini.Handler { func RequireTrueOwner() macaron.Handler {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.Repo.IsTrueOwner { if !ctx.Repo.IsTrueOwner {
if !ctx.IsSigned { if !ctx.IsSigned {

@ -1,127 +0,0 @@
// Copyright 2013 The Martini Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package middleware
import (
"log"
"net/http"
"path"
"runtime"
"strings"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/setting"
)
// StaticOptions is a struct for specifying configuration options for the martini.Static middleware.
type StaticOptions struct {
// Prefix is the optional prefix used to serve the static directory content
Prefix string
// SkipLogging will disable [Static] log messages when a static file is served.
SkipLogging bool
// IndexFile defines which file to serve as index if it exists.
IndexFile string
// Expires defines which user-defined function to use for producing a HTTP Expires Header
// https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
Expires func() string
}
func prepareStaticOptions(options []StaticOptions) StaticOptions {
var opt StaticOptions
if len(options) > 0 {
opt = options[0]
}
// Defaults
if len(opt.IndexFile) == 0 {
opt.IndexFile = "index.html"
}
// Normalize the prefix if provided
if opt.Prefix != "" {
// Ensure we have a leading '/'
if opt.Prefix[0] != '/' {
opt.Prefix = "/" + opt.Prefix
}
// Remove any trailing '/'
opt.Prefix = strings.TrimRight(opt.Prefix, "/")
}
return opt
}
// Static returns a middleware handler that serves static files in the given directory.
func Static(directory string, staticOpt ...StaticOptions) martini.Handler {
if runtime.GOOS == "windows" {
if len(directory) < 2 || directory[1] != ':' {
directory = path.Join(setting.StaticRootPath, directory)
}
} else if !path.IsAbs(directory) {
directory = path.Join(setting.StaticRootPath, directory)
}
dir := http.Dir(directory)
opt := prepareStaticOptions(staticOpt)
return func(res http.ResponseWriter, req *http.Request, log *log.Logger) {
if req.Method != "GET" && req.Method != "HEAD" {
return
}
file := req.URL.Path
// if we have a prefix, filter requests by stripping the prefix
if opt.Prefix != "" {
if !strings.HasPrefix(file, opt.Prefix) {
return
}
file = file[len(opt.Prefix):]
if file != "" && file[0] != '/' {
return
}
}
f, err := dir.Open(file)
if err != nil {
// discard the error?
return
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return
}
// try to serve index file
if fi.IsDir() {
// redirect if missing trailing slash
if !strings.HasSuffix(req.URL.Path, "/") {
http.Redirect(res, req, req.URL.Path+"/", http.StatusFound)
return
}
file = path.Join(file, opt.IndexFile)
f, err = dir.Open(file)
if err != nil {
return
}
defer f.Close()
fi, err = f.Stat()
if err != nil || fi.IsDir() {
return
}
}
if !opt.SkipLogging {
log.Println("[Static] Serving " + file)
}
// Add an Expires header to the static content
if opt.Expires != nil {
res.Header().Set("Expires", opt.Expires())
}
http.ServeContent(res, req, file, fi.ModTime(), f)
}
}

@ -78,7 +78,7 @@ func ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (
select { select {
case <-time.After(timeout): case <-time.After(timeout):
if errKill := Kill(pid); errKill != nil { if errKill := Kill(pid); errKill != nil {
log.Error("Exec(%d:%s): %v", pid, desc, errKill) log.Error(4, "Exec(%d:%s): %v", pid, desc, errKill)
} }
<-done <-done
return "", ErrExecTimeout.Error(), ErrExecTimeout return "", ErrExecTimeout.Error(), ErrExecTimeout

@ -15,12 +15,12 @@ import (
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/Unknwon/goconfig" "github.com/Unknwon/goconfig"
"github.com/macaron-contrib/session"
"github.com/gogits/cache" "github.com/gogits/cache"
"github.com/gogits/session"
"github.com/gogits/gogs/modules/bin"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
// "github.com/gogits/gogs-ng/modules/ssh"
) )
type Scheme string type Scheme string
@ -46,6 +46,7 @@ var (
DisableRouterLog bool DisableRouterLog bool
CertFile, KeyFile string CertFile, KeyFile string
StaticRootPath string StaticRootPath string
EnableGzip bool
// Security settings. // Security settings.
InstallLock bool InstallLock bool
@ -93,15 +94,22 @@ var (
// Session settings. // Session settings.
SessionProvider string SessionProvider string
SessionConfig *session.Config SessionConfig *session.Config
SessionManager *session.Manager
// Global setting objects. // Global setting objects.
Cfg *goconfig.ConfigFile Cfg *goconfig.ConfigFile
CustomPath string // Custom directory path. ConfRootPath string
ProdMode bool CustomPath string // Custom directory path.
RunUser string ProdMode bool
RunUser string
// I18n settings.
Langs, Names []string
) )
func init() {
log.NewLogger(0, "console", `{"level": 0}`)
}
func ExecPath() (string, error) { func ExecPath() (string, error) {
file, err := exec.LookPath(os.Args[0]) file, err := exec.LookPath(os.Args[0])
if err != nil { if err != nil {
@ -125,16 +133,13 @@ func WorkDir() (string, error) {
func NewConfigContext() { func NewConfigContext() {
workDir, err := WorkDir() workDir, err := WorkDir()
if err != nil { if err != nil {
log.Fatal("Fail to get work directory: %v", err) log.Fatal(4, "Fail to get work directory: %v", err)
} }
ConfRootPath = path.Join(workDir, "conf")
data, err := bin.Asset("conf/app.ini") Cfg, err = goconfig.LoadConfigFile(path.Join(workDir, "conf/app.ini"))
if err != nil { if err != nil {
log.Fatal("Fail to read 'conf/app.ini': %v", err) log.Fatal(4, "Fail to parse 'conf/app.ini': %v", err)
}
Cfg, err = goconfig.LoadFromData(data)
if err != nil {
log.Fatal("Fail to parse 'conf/app.ini': %v", err)
} }
CustomPath = os.Getenv("GOGS_CUSTOM") CustomPath = os.Getenv("GOGS_CUSTOM")
@ -145,10 +150,10 @@ func NewConfigContext() {
cfgPath := path.Join(CustomPath, "conf/app.ini") cfgPath := path.Join(CustomPath, "conf/app.ini")
if com.IsFile(cfgPath) { if com.IsFile(cfgPath) {
if err = Cfg.AppendFiles(cfgPath); err != nil { if err = Cfg.AppendFiles(cfgPath); err != nil {
log.Fatal("Fail to load custom 'conf/app.ini': %v", err) log.Fatal(4, "Fail to load custom 'conf/app.ini': %v", err)
} }
} else { } else {
log.Warn("No custom 'conf/app.ini' found") log.Warn("No custom 'conf/app.ini' found, please go to '/install'")
} }
AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service") AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service")
@ -169,6 +174,7 @@ func NewConfigContext() {
DisableRouterLog = Cfg.MustBool("server", "DISABLE_ROUTER_LOG") DisableRouterLog = Cfg.MustBool("server", "DISABLE_ROUTER_LOG")
StaticRootPath = Cfg.MustValue("server", "STATIC_ROOT_PATH", workDir) StaticRootPath = Cfg.MustValue("server", "STATIC_ROOT_PATH", workDir)
LogRootPath = Cfg.MustValue("log", "ROOT_PATH", path.Join(workDir, "log")) LogRootPath = Cfg.MustValue("log", "ROOT_PATH", path.Join(workDir, "log"))
EnableGzip = Cfg.MustBool("server", "ENABLE_GZIP")
InstallLock = Cfg.MustBool("security", "INSTALL_LOCK") InstallLock = Cfg.MustBool("security", "INSTALL_LOCK")
SecretKey = Cfg.MustValue("security", "SECRET_KEY") SecretKey = Cfg.MustValue("security", "SECRET_KEY")
@ -177,8 +183,8 @@ func NewConfigContext() {
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME") CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER") ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER")
AttachmentPath = Cfg.MustValue("attachment", "PATH", "files/attachments") AttachmentPath = Cfg.MustValue("attachment", "PATH", "data/attachments")
AttachmentAllowedTypes = Cfg.MustValue("attachment", "ALLOWED_TYPES", "*/*") AttachmentAllowedTypes = Cfg.MustValue("attachment", "ALLOWED_TYPES", "image/jpeg|image/png")
AttachmentMaxSize = Cfg.MustInt64("attachment", "MAX_SIZE", 32) AttachmentMaxSize = Cfg.MustInt64("attachment", "MAX_SIZE", 32)
AttachmentMaxFiles = Cfg.MustInt("attachment", "MAX_FILES", 10) AttachmentMaxFiles = Cfg.MustInt("attachment", "MAX_FILES", 10)
AttachmentEnabled = Cfg.MustBool("attachment", "ENABLE", true) AttachmentEnabled = Cfg.MustBool("attachment", "ENABLE", true)
@ -233,7 +239,7 @@ func NewConfigContext() {
} }
if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil { if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil {
log.Fatal("Could not create directory %s: %s", AttachmentPath, err) log.Fatal(4, "Could not create directory %s: %s", AttachmentPath, err)
} }
RunUser = Cfg.MustValue("", "RUN_USER") RunUser = Cfg.MustValue("", "RUN_USER")
@ -243,13 +249,13 @@ func NewConfigContext() {
} }
// Does not check run user when the install lock is off. // Does not check run user when the install lock is off.
if InstallLock && RunUser != curUser { if InstallLock && RunUser != curUser {
log.Fatal("Expect user(%s) but current user is: %s", RunUser, curUser) log.Fatal(4, "Expect user(%s) but current user is: %s", RunUser, curUser)
} }
// Determine and create root git reposiroty path. // Determine and create root git reposiroty path.
homeDir, err := com.HomeDir() homeDir, err := com.HomeDir()
if err != nil { if err != nil {
log.Fatal("Fail to get home directory: %v", err) log.Fatal(4, "Fail to get home directory: %v", err)
} }
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories")) RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
if !filepath.IsAbs(RepoRootPath) { if !filepath.IsAbs(RepoRootPath) {
@ -259,13 +265,16 @@ func NewConfigContext() {
} }
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil { if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
log.Fatal("Fail to create repository root path(%s): %v", RepoRootPath, err) log.Fatal(4, "Fail to create repository root path(%s): %v", RepoRootPath, err)
} }
ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash") ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
PictureService = Cfg.MustValueRange("picture", "SERVICE", "server", PictureService = Cfg.MustValueRange("picture", "SERVICE", "server",
[]string{"server"}) []string{"server"})
DisableGravatar = Cfg.MustBool("picture", "DISABLE_GRAVATAR") DisableGravatar = Cfg.MustBool("picture", "DISABLE_GRAVATAR")
Langs = Cfg.MustValueArray("i18n", "LANGS", ",")
Names = Cfg.MustValueArray("i18n", "NAMES", ",")
} }
var Service struct { var Service struct {
@ -308,7 +317,7 @@ func newLogService() {
mode = strings.TrimSpace(mode) mode = strings.TrimSpace(mode)
modeSec := "log." + mode modeSec := "log." + mode
if _, err := Cfg.GetSection(modeSec); err != nil { if _, err := Cfg.GetSection(modeSec); err != nil {
log.Fatal("Unknown log mode: %s", mode) log.Fatal(4, "Unknown log mode: %s", mode)
} }
// Log level. // Log level.
@ -316,7 +325,7 @@ func newLogService() {
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"}) []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
level, ok := logLevels[levelName] level, ok := logLevels[levelName]
if !ok { if !ok {
log.Fatal("Unknown log level: %s", levelName) log.Fatal(4, "Unknown log level: %s", levelName)
} }
// Generate log configuration. // Generate log configuration.
@ -371,15 +380,15 @@ func newCacheService() {
case "memory": case "memory":
CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60)) CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60))
case "redis", "memcache": case "redis", "memcache":
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST")) CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, strings.Trim(Cfg.MustValue("cache", "HOST"), "\" "))
default: default:
log.Fatal("Unknown cache adapter: %s", CacheAdapter) log.Fatal(4, "Unknown cache adapter: %s", CacheAdapter)
} }
var err error var err error
Cache, err = cache.NewCache(CacheAdapter, CacheConfig) Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
if err != nil { if err != nil {
log.Fatal("Init cache system failed, adapter: %s, config: %s, %v\n", log.Fatal(4, "Init cache system failed, adapter: %s, config: %s, %v\n",
CacheAdapter, CacheConfig, err) CacheAdapter, CacheConfig, err)
} }
@ -391,12 +400,12 @@ func newSessionService() {
[]string{"memory", "file", "redis", "mysql"}) []string{"memory", "file", "redis", "mysql"})
SessionConfig = new(session.Config) SessionConfig = new(session.Config)
SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG") SessionConfig.ProviderConfig = strings.Trim(Cfg.MustValue("session", "PROVIDER_CONFIG"), "\" ")
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits") SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE") SessionConfig.Secure = Cfg.MustBool("session", "COOKIE_SECURE")
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true) SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400) SessionConfig.Gclifetime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400) SessionConfig.Maxlifetime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
SessionConfig.SessionIDHashFunc = Cfg.MustValueRange("session", "SESSION_ID_HASHFUNC", SessionConfig.SessionIDHashFunc = Cfg.MustValueRange("session", "SESSION_ID_HASHFUNC",
"sha1", []string{"sha1", "sha256", "md5"}) "sha1", []string{"sha1", "sha256", "md5"})
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY") SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY")
@ -405,14 +414,6 @@ func newSessionService() {
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm) os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
} }
var err error
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
if err != nil {
log.Fatal("Init session system failed, provider: %s, %v",
SessionProvider, err)
}
go SessionManager.GC()
log.Info("Session Service Enabled") log.Info("Session Service Enabled")
} }
@ -494,4 +495,5 @@ func NewServices() {
newRegisterMailService() newRegisterMailService()
newNotifyMailService() newNotifyMailService()
newWebhookService() newWebhookService()
// ssh.Listen("2022")
} }

@ -0,0 +1,119 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Prototype, git client looks like do not recognize req.Reply.
package ssh
import (
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"strings"
"code.google.com/p/go.crypto/ssh"
"github.com/Unknwon/com"
"github.com/gogits/gogs/modules/log"
)
func handleServerConn(keyId string, chans <-chan ssh.NewChannel) {
for newChan := range chans {
if newChan.ChannelType() != "session" {
newChan.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChan.Accept()
if err != nil {
log.Error(3, "Could not accept channel: %v", err)
continue
}
go func(in <-chan *ssh.Request) {
defer channel.Close()
for req := range in {
ok, payload := false, strings.TrimLeft(string(req.Payload), "\x00")
fmt.Println("Request:", req.Type, req.WantReply, payload)
switch req.Type {
case "env":
args := strings.Split(strings.Replace(payload, "\x00", "", -1), "\v")
if len(args) != 2 {
break
}
args[0] = strings.TrimLeft(args[0], "\x04")
_, _, err := com.ExecCmdBytes("env", args[0]+"="+args[1])
if err != nil {
log.Error(3, "env: %v", err)
channel.Stderr().Write([]byte(err.Error()))
break
}
ok = true
case "exec":
os.Setenv("SSH_ORIGINAL_COMMAND", strings.TrimLeft(payload, "'("))
log.Info("Payload: %v", strings.TrimLeft(payload, "'("))
cmd := exec.Command("/Users/jiahuachen/Applications/Go/src/github.com/gogits/gogs-ng/gogs-ng", "serv", "key-"+keyId)
cmd.Stdout = channel
cmd.Stdin = channel
cmd.Stderr = channel.Stderr()
if err := cmd.Run(); err != nil {
log.Error(3, "exec: %v", err)
} else {
ok = true
}
}
fmt.Println("Done:", ok)
req.Reply(ok, nil) // BUG: Git on Mac seems not know this reply and hang?
}
fmt.Println("Done!!!")
}(requests)
}
}
func listen(config *ssh.ServerConfig, port string) {
listener, err := net.Listen("tcp", "0.0.0.0:"+port)
if err != nil {
panic(err)
}
for {
// Once a ServerConfig has been configured, connections can be accepted.
conn, err := listener.Accept()
if err != nil {
log.Error(3, "Fail to accept incoming connection: %v", err)
continue
}
// Before use, a handshake must be performed on the incoming net.Conn.
sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
log.Error(3, "Fail to handshake: %v", err)
continue
}
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
go handleServerConn(sConn.Permissions.Extensions["key-id"], chans)
}
}
// Listen starts a SSH server listens on given port.
func Listen(port string) {
config := &ssh.ServerConfig{
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
// keyCache[string(ssh.MarshalAuthorizedKey(key))] = 2
return &ssh.Permissions{Extensions: map[string]string{"key-id": "2"}}, nil
},
}
privateBytes, err := ioutil.ReadFile("/Users/jiahuachen/.ssh/id_rsa")
if err != nil {
panic("failed to load private key")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
panic("failed to parse private key")
}
config.AddHostKey(private)
go listen(config, port)
}

@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-template_comment,.diff .hljs-header,.hljs-javadoc{color:#998;font-style:italic}.hljs-keyword,.css .rule .hljs-keyword,.hljs-winutils,.javascript .hljs-title,.nginx .hljs-title,.hljs-subst,.hljs-request,.hljs-status{color:#333;font-weight:bold}.hljs-number,.hljs-hexcolor,.ruby .hljs-constant{color:#099}.hljs-string,.hljs-tag .hljs-value,.hljs-phpdoc,.tex .hljs-formula{color:#d14}.hljs-title,.hljs-id,.coffeescript .hljs-params,.scss .hljs-preprocessor{color:#900;font-weight:bold}.javascript .hljs-title,.lisp .hljs-title,.clojure .hljs-title,.hljs-subst{font-weight:normal}.hljs-class .hljs-title,.haskell .hljs-type,.vhdl .hljs-literal,.tex .hljs-command{color:#458;font-weight:bold}.hljs-tag,.hljs-tag .hljs-title,.hljs-rules .hljs-property,.django .hljs-tag .hljs-keyword{color:#000080;font-weight:normal}.hljs-attribute,.hljs-variable,.lisp .hljs-body{color:#008080}.hljs-regexp{color:#009926}.hljs-symbol,.ruby .hljs-symbol .hljs-string,.lisp .hljs-keyword,.tex .hljs-special,.hljs-prompt{color:#990073}.hljs-built_in,.lisp .hljs-title,.clojure .hljs-built_in{color:#0086b3}.hljs-preprocessor,.hljs-pragma,.hljs-pi,.hljs-doctype,.hljs-shebang,.hljs-cdata{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.diff .hljs-change{background:#0086b3}.hljs-chunk{color:#aaa}

@ -1837,3 +1837,10 @@ body {
#issue-create-form #attached { #issue-create-form #attached {
margin-bottom: 0; margin-bottom: 0;
} }
#submit-error {
display: none;
padding: 10px 15px 15px 15px;
font-weight: bold;
text-align: center;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

@ -520,6 +520,50 @@ function initIssue() {
}); });
}()); }());
// store unsend text in session storage.
(function() {
var $textArea = $("#issue-content,#issue-reply-content");
var current = "";
if ($textArea == null || !('sessionStorage' in window)) {
return;
}
var path = location.pathname.split("/");
var key = "issue-" + path[1] + "-" + path[2] + "-";
if (/\/issues\/\d+$/.test(location.pathname)) {
key = key + path[4];
} else {
key = key + "new";
}
if ($textArea.val() !== undefined && $textArea.val() !== "") {
sessionStorage.setItem(key, $textArea.val());
} else {
$textArea.val(sessionStorage.getItem(key) || "");
if ($textArea.attr("id") == "issue-reply-content") {
var $closeBtn = $('#issue-close-btn');
var $openBtn = $('#issue-open-btn');
if ($textArea.val().length) {
$closeBtn.val($closeBtn.data("text"));
$openBtn.val($openBtn.data("text"));
} else {
$closeBtn.val($closeBtn.data("origin"));
$openBtn.val($openBtn.data("origin"));
}
}
}
$textArea.on("keyup", function() {
if ($textArea.val() !== current) {
sessionStorage.setItem(key, current = $textArea.val());
}
});
}());
// Preview for images. // Preview for images.
(function() { (function() {
var $hoverElement = $("<div></div>"); var $hoverElement = $("<div></div>");
@ -536,7 +580,7 @@ function initIssue() {
var over = function() { var over = function() {
var $this = $(this); var $this = $(this);
if ($this.text().match(/\.(png|jpg|jpeg|gif)$/i) == false) { if ((/\.(png|jpg|jpeg|gif)$/i).test($this.text()) == false) {
return; return;
} }
@ -579,23 +623,135 @@ function initIssue() {
var $attachedList = $("#attached-list"); var $attachedList = $("#attached-list");
var $addButton = $("#attachments-button"); var $addButton = $("#attachments-button");
var fileInput = $("#attachments-input")[0]; var files = [];
fileInput.addEventListener("change", function(event) { var fileInput = document.getElementById("attachments-input");
$attachedList.empty();
$attachedList.append("<b>Attachments:</b> "); if (fileInput === null) {
return;
}
$attachedList.on("click", "span.attachment-remove", function(event) {
var $parent = $(this).parent();
files.splice($parent.data("index"), 1);
$parent.remove();
});
var clickedButton = undefined;
$("button,input[type=\"submit\"]", fileInput.form).on("click", function() {
clickedButton = this;
var $button = $(this);
$button.removeClass("btn-success");
$button.addClass("btn-warning");
$button.text("Submiting...");
});
fileInput.form.addEventListener("submit", function(event) {
event.stopImmediatePropagation();
event.preventDefault();
//var data = new FormData(this);
// Internet Explorer ... -_-
var data = new FormData();
$.each($("[name]", this), function(i, e) {
if (e.name == "attachments" || e.type == "submit") {
return;
}
data.append(e.name, $(e).val());
});
data.append(clickedButton.name, $(clickedButton).val());
files.forEach(function(file) {
data.append("attachments", file);
});
var xhr = new XMLHttpRequest();
xhr.addEventListener("error", function() {
debugger;
});
xhr.addEventListener("load", function() {
var response = xhr.response;
if (typeof response == "string") {
try {
response = JSON.parse(response);
} catch (err) {
response = { ok: false, error: "Could not parse JSON" };
}
}
if (response.ok === false) {
$("#submit-error").text(response.error);
$("#submit-error").show();
var $button = $(clickedButton);
$button.removeClass("btn-warning");
$button.addClass("btn-danger");
$button.text("An error encoured!")
return;
}
if (!('sessionStorage' in window)) {
return;
}
var path = location.pathname.split("/");
var key = "issue-" + path[1] + "-" + path[2] + "-";
if (/\/issues\/\d+$/.test(location.pathname)) {
key = key + path[4];
} else {
key = key + "new";
}
sessionStorage.removeItem(key);
window.location.href = response.data;
});
xhr.open("POST", this.action, true);
xhr.send(data);
return false;
});
fileInput.addEventListener("change", function(event) {
for (var index = 0; index < fileInput.files.length; index++) { for (var index = 0; index < fileInput.files.length; index++) {
var file = fileInput.files[index]; var file = fileInput.files[index];
if (files.indexOf(file) > -1) {
continue;
}
var $span = $("<span></span>"); var $span = $("<span></span>");
$span.addClass("label"); $span.addClass("label");
$span.addClass("label-default"); $span.addClass("label-default");
$span.append(file.name.toLowerCase()); $span.data("index", files.length);
$span.append(file.name);
$span.append(" <span class=\"attachment-remove fa fa-times-circle\"></span>");
$attachedList.append($span); $attachedList.append($span);
files.push(file);
} }
this.value = "";
}); });
$addButton.on("click", function() { $addButton.on("click", function() {
@ -828,12 +984,18 @@ function initIssue() {
$(item).addClass("no-checked"); $(item).addClass("no-checked");
$("#label-" + id, $labels).remove(); $("#label-" + id, $labels).remove();
if ($labels.children(".label-item").length == 0) {
$labels.append("<p>None yet</p>");
}
} else { } else {
$(item).prepend('<span class="check pull-left"><i class="fa fa-check"></i></span>'); $(item).prepend('<span class="check pull-left"><i class="fa fa-check"></i></span>');
$(item).removeClass("no-checked"); $(item).removeClass("no-checked");
$(item).addClass("checked"); $(item).addClass("checked");
$("p:not([class])", $labels).remove();
var $l = $("<p></p>"); var $l = $("<p></p>");
var c = $("span.color", item).css("background-color"); var c = $("span.color", item).css("background-color");

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save