aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2016-01-24 23:23:37 +0000
committerVsevolod Stakhov <vsevolod@highsecure.ru>2016-01-24 23:23:37 +0000
commitd100debaa37a7477fa06bb95ca5087ed70f99cba (patch)
treec913d1c541646f68b95d444f0b0ce00218bf8fc6
parent1678075da31b6939396a95b9aa2c93c90ee7485b (diff)
downloadrspamd-d100debaa37a7477fa06bb95ca5087ed70f99cba.tar.gz
rspamd-d100debaa37a7477fa06bb95ca5087ed70f99cba.zip
Rework parsing of IP addresses
-rw-r--r--src/libserver/cfg_utils.c48
-rw-r--r--src/libutil/addr.c316
-rw-r--r--src/libutil/addr.h14
3 files changed, 173 insertions, 205 deletions
diff --git a/src/libserver/cfg_utils.c b/src/libserver/cfg_utils.c
index 32651dd20..475850f95 100644
--- a/src/libserver/cfg_utils.c
+++ b/src/libserver/cfg_utils.c
@@ -63,59 +63,23 @@ rspamd_parse_bind_line (struct rspamd_config *cfg,
const gchar *str)
{
struct rspamd_worker_bind_conf *cnf;
- gchar **tokens, *err;
+ gchar *err;
gboolean ret = TRUE;
if (str == NULL) {
return FALSE;
}
- if (str[0] == '[') {
- /* This is an ipv6 address */
- gsize len, ntok;
- const gchar *start, *ip_pos;
-
- start = str + 1;
-
- len = strcspn (start, "]");
- if (start[len] != ']') {
- return FALSE;
- }
-
- ip_pos = start;
- start += len + 1;
- ntok = 1;
-
- if (*start == ':') {
- ntok = 2;
- start ++;
- }
-
- tokens = g_malloc_n (ntok + 1, sizeof (gchar *));
- tokens[ntok] = NULL;
- tokens[0] = g_malloc (len + 1);
- rspamd_strlcpy (tokens[0], ip_pos, len + 1);
-
- if (ntok > 1) {
- tokens[1] = g_strdup (start);
- }
- }
- else {
- tokens = g_strsplit_set (str, ":", 0);
- }
- if (!tokens || !tokens[0]) {
- return FALSE;
- }
-
cnf =
rspamd_mempool_alloc0 (cfg->cfg_pool,
sizeof (struct rspamd_worker_bind_conf));
cnf->cnt = 1024;
- if (strcmp (tokens[0], "systemd") == 0) {
+
+ if (strcmp (str, "systemd") == 0) {
/* The actual socket will be passed by systemd environment */
cnf->is_systemd = TRUE;
- cnf->cnt = strtoul (tokens[1], &err, 10);
+ cnf->cnt = strtoul (str, &err, 10);
cnf->addrs = NULL;
if (err == NULL || *err == '\0') {
@@ -128,7 +92,7 @@ rspamd_parse_bind_line (struct rspamd_config *cfg,
}
}
else {
- if (!rspamd_parse_host_port_priority_strv (tokens, &cnf->addrs,
+ if (!rspamd_parse_host_port_priority (str, &cnf->addrs,
NULL, &cnf->name, DEFAULT_BIND_PORT, cfg->cfg_pool)) {
msg_err_config ("cannot parse bind line: %s", str);
ret = FALSE;
@@ -139,8 +103,6 @@ rspamd_parse_bind_line (struct rspamd_config *cfg,
}
}
- g_strfreev (tokens);
-
return ret;
}
diff --git a/src/libutil/addr.c b/src/libutil/addr.c
index 3eeef8e44..f5d1415d9 100644
--- a/src/libutil/addr.c
+++ b/src/libutil/addr.c
@@ -879,34 +879,68 @@ rspamd_inet_address_sendto (gint fd, const void *buf, gsize len, gint fl,
return r;
}
-gboolean
-rspamd_parse_host_port_priority_strv (gchar **tokens,
- GPtrArray **addrs,
- guint *priority,
- gchar **name,
- guint default_port,
- rspamd_mempool_t *pool)
+static gboolean
+rspamd_check_port_priority (const char *line, guint default_port,
+ guint *priority, gchar *out,
+ gsize outlen, rspamd_mempool_t *pool)
+{
+ guint real_port = default_port, real_priority = 0;
+ gchar *err_str, *err_str_prio;
+
+ if (line && line[0] == ':') {
+ errno = 0;
+ real_port = strtoul (line + 1, &err_str, 10);
+
+ if (err_str && *err_str == ':') {
+ /* We have priority */
+ real_priority = strtoul (err_str + 1, &err_str_prio, 10);
+
+ if (err_str_prio && *err_str_prio != '\0') {
+ msg_err_pool (
+ "cannot parse priority: %s, at symbol %c, error: %s",
+ line,
+ *err_str_prio,
+ strerror (errno));
+
+ return FALSE;
+ }
+ }
+ else if (err_str && *err_str != '\0') {
+ msg_err_pool (
+ "cannot parse port: %s, at symbol %c, error: %s",
+ line,
+ *err_str,
+ strerror (errno));
+
+ return FALSE;
+ }
+ }
+
+ if (priority) {
+ *priority = real_priority;
+ }
+
+ rspamd_snprintf (out, outlen, "%ud", real_port);
+
+ return TRUE;
+}
+
+static gboolean
+rspamd_resolve_addrs (const char *begin, size_t len, GPtrArray **addrs,
+ const gchar *portbuf, gint flags,
+ rspamd_mempool_t *pool)
{
- gchar *err_str, portbuf[8];
- const gchar *cur_tok, *cur_port;
struct addrinfo hints, *res, *cur;
- rspamd_inet_addr_t *cur_addr;
- guint addr_cnt;
- guint port_parsed, priority_parsed, saved_errno = errno;
- gint r;
+ rspamd_inet_addr_t *cur_addr = NULL;
+ gint r, addr_cnt;
+ gchar *addr_cpy;
rspamd_ip_check_ipv6 ();
- /* Now try to parse host and write address to ina */
memset (&hints, 0, sizeof (hints));
hints.ai_socktype = SOCK_STREAM; /* Type of the socket */
- hints.ai_flags = AI_NUMERICSERV;
-
- cur_tok = tokens[0];
-
- if (strcmp (cur_tok, "*") == 0) {
- hints.ai_flags |= AI_PASSIVE;
- cur_tok = NULL;
- }
+ hints.ai_flags = AI_NUMERICSERV|flags;
+ addr_cpy = g_malloc (len + 1);
+ rspamd_strlcpy (addr_cpy, begin, len + 1);
if (ipv6_status == RSPAMD_IPV6_SUPPORTED) {
hints.ai_family = AF_UNSPEC;
@@ -915,166 +949,152 @@ rspamd_parse_host_port_priority_strv (gchar **tokens,
hints.ai_family = AF_INET;
}
- if (tokens[1] != NULL) {
- /* Port part */
- rspamd_strlcpy (portbuf, tokens[1], sizeof (portbuf));
- cur_port = portbuf;
- errno = 0;
- port_parsed = strtoul (tokens[1], &err_str, 10);
- if (*err_str != '\0' || errno != 0) {
- msg_warn ("cannot parse port: %s, at symbol %c, error: %s",
- tokens[1],
- *err_str,
- strerror (errno));
- hints.ai_flags ^= AI_NUMERICSERV;
- }
- else if (port_parsed > G_MAXUINT16) {
- errno = ERANGE;
- msg_warn ("cannot parse port: %s, error: %s",
- tokens[1],
- strerror (errno));
- hints.ai_flags ^= AI_NUMERICSERV;
+ if ((r = getaddrinfo (addr_cpy, portbuf, &hints, &res)) == 0) {
+ /* Now copy up to max_addrs of addresses */
+ addr_cnt = 0;
+ cur = res;
+ while (cur) {
+ cur = cur->ai_next;
+ addr_cnt ++;
}
- if (priority != NULL) {
- const gchar *tok;
-
- tok = tokens[2];
- if (tok != NULL) {
- /* Priority part */
- errno = 0;
- priority_parsed = strtoul (tok, &err_str, 10);
- if (*err_str != '\0' || errno != 0) {
- msg_warn (
- "cannot parse priority: %s, at symbol %c, error: %s",
- tok,
- *err_str,
- strerror (errno));
- }
- else {
- *priority = priority_parsed;
- }
- }
- }
- }
- else if (default_port != 0) {
- rspamd_snprintf (portbuf, sizeof (portbuf), "%ud", default_port);
- cur_port = portbuf;
- }
- else {
- cur_port = NULL;
- }
-
- if (*tokens[0] != '/') {
- if ((r = getaddrinfo (cur_tok, cur_port, &hints, &res)) == 0) {
- /* Now copy up to max_addrs of addresses */
- addr_cnt = 0;
- cur = res;
- while (cur) {
- cur = cur->ai_next;
- addr_cnt ++;
- }
+ if (*addrs == NULL) {
*addrs = g_ptr_array_new_full (addr_cnt,
- (GDestroyNotify)rspamd_inet_address_destroy);
+ (GDestroyNotify)rspamd_inet_address_destroy);
if (pool != NULL) {
rspamd_mempool_add_destructor (pool,
rspamd_ptr_array_free_hard, *addrs);
}
+ }
- cur = res;
- while (cur) {
- cur_addr = rspamd_inet_address_from_sa (cur->ai_addr,
- cur->ai_addrlen);
+ cur = res;
+ while (cur) {
+ cur_addr = rspamd_inet_address_from_sa (cur->ai_addr,
+ cur->ai_addrlen);
- if (cur_addr != NULL) {
- g_ptr_array_add (*addrs, cur_addr);
- }
- cur = cur->ai_next;
+ if (cur_addr != NULL) {
+ g_ptr_array_add (*addrs, cur_addr);
}
-
- freeaddrinfo (res);
- }
- else {
- msg_err ("address resolution for %s failed: %s",
- tokens[0],
- gai_strerror (r));
- goto err;
+ cur = cur->ai_next;
}
+
+ freeaddrinfo (res);
}
else {
- /* Special case of unix socket, as getaddrinfo cannot deal with them */
- *addrs = g_ptr_array_new_full (1,
- (GDestroyNotify)rspamd_inet_address_destroy);
-
- if (pool != NULL) {
- rspamd_mempool_add_destructor (pool,
- rspamd_ptr_array_free_hard, *addrs);
- }
-
- if (!rspamd_parse_inet_address (&cur_addr, tokens[0], 0)) {
- msg_err ("cannot parse unix socket definition %s: %s",
- tokens[0],
- strerror (errno));
- goto err;
- }
+ msg_err_pool ("address resolution for %s failed: %s",
+ addr_cpy,
+ gai_strerror (r));
+ g_free (addr_cpy);
- g_ptr_array_add (*addrs, cur_addr);
+ return FALSE;
}
- /* Restore errno */
- if (name != NULL) {
- if (pool == NULL) {
- *name = g_strdup (tokens[0]);
- }
- else {
- *name = rspamd_mempool_strdup (pool, tokens[0]);
- }
- }
- errno = saved_errno;
return TRUE;
-
-err:
- errno = saved_errno;
- return FALSE;
}
gboolean
-rspamd_parse_host_port_priority (
- const gchar *str,
+rspamd_parse_host_port_priority (const gchar *str,
GPtrArray **addrs,
guint *priority,
gchar **name,
guint default_port,
rspamd_mempool_t *pool)
{
- gchar **tokens;
- gboolean ret;
+ gchar portbuf[8];
+ const gchar *p;
+ rspamd_inet_addr_t *cur_addr = NULL;
+
+ /*
+ * In this function, we can have several possibilities:
+ * 1) Unix socket: check for '.' or '/' at the begin of string
+ * 2) \[ipv6\]: check for '[' at the beginning
+ * 3) '*': means listening on any address
+ * 4) ip|host[:port[:priority]]
+ */
+
+ if (str[0] == '*') {
+ if (!rspamd_check_port_priority (str + 1, default_port, priority,
+ portbuf, sizeof (portbuf), pool)) {
+ return FALSE;
+ }
- tokens = g_strsplit_set (str, ":", 0);
- if (!tokens || !tokens[0]) {
- return FALSE;
+ if (!rspamd_resolve_addrs (str, 1, addrs, portbuf, AI_PASSIVE, pool)) {
+ return FALSE;
+ }
}
+ else if (str[0] == '[') {
+ /* This is braced IPv6 address */
+ p = strchr (str, ']');
- ret = rspamd_parse_host_port_priority_strv (tokens, addrs,
- priority, name, default_port, pool);
+ if (p == NULL) {
+ msg_err ("cannot parse address definition %s: %s",
+ str,
+ strerror (EINVAL));
- g_strfreev (tokens);
+ return FALSE;
+ }
- return ret;
-}
+ if (!rspamd_check_port_priority (p + 1, default_port, priority, portbuf,
+ sizeof (portbuf), pool)) {
+ return FALSE;
+ }
-gboolean
-rspamd_parse_host_port (const gchar *str,
- GPtrArray **addrs,
- gchar **name,
- guint default_port,
- rspamd_mempool_t *pool)
-{
- return rspamd_parse_host_port_priority (str, addrs, NULL,
- name, default_port, pool);
-}
+ if (!rspamd_resolve_addrs (str + 1, p - str, addrs,
+ portbuf, 0, pool)) {
+ return FALSE;
+ }
+ }
+ else if (str[0] == '/' || str[0] == '.') {
+ /* Special case of unix socket, as getaddrinfo cannot deal with them */
+ if (*addrs == NULL) {
+ *addrs = g_ptr_array_new_full (1,
+ (GDestroyNotify)rspamd_inet_address_destroy);
+
+ if (pool != NULL) {
+ rspamd_mempool_add_destructor (pool,
+ rspamd_ptr_array_free_hard, *addrs);
+ }
+ }
+
+ if (!rspamd_parse_inet_address (&cur_addr, str, 0)) {
+ msg_err_pool ("cannot parse unix socket definition %s: %s",
+ str,
+ strerror (errno));
+
+ return FALSE;
+ }
+
+ g_ptr_array_add (*addrs, cur_addr);
+ }
+ else {
+ p = strchr (str, ':');
+ if (p == NULL) {
+ /* Just address or IP */
+ rspamd_check_port_priority ("", default_port, priority, portbuf,
+ sizeof (portbuf), pool);
+
+ if (!rspamd_resolve_addrs (str, strlen (str), addrs,
+ portbuf, 0, pool)) {
+ return FALSE;
+ }
+ }
+ else {
+ if (!rspamd_check_port_priority (p + 1, default_port, priority, portbuf,
+ sizeof (portbuf), pool)) {
+ return FALSE;
+ }
+
+ if (!rspamd_resolve_addrs (str + 1, p - str, addrs,
+ portbuf, 0, pool)) {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
guchar*
rspamd_inet_address_get_radix_key (const rspamd_inet_addr_t *addr, guint *klen)
diff --git a/src/libutil/addr.h b/src/libutil/addr.h
index b2075f7f0..72d7beb07 100644
--- a/src/libutil/addr.h
+++ b/src/libutil/addr.h
@@ -189,10 +189,6 @@ gboolean rspamd_ip_is_valid (const rspamd_inet_addr_t *addr);
*/
gint rspamd_accept_from_socket (gint sock, rspamd_inet_addr_t **addr);
-gboolean rspamd_parse_host_port_priority_strv (gchar **tokens,
- GPtrArray **addrs, guint *priority,
- gchar **name, guint default_port, rspamd_mempool_t *pool);
-
/**
* Parse host[:port[:priority]] line
* @param ina host address
@@ -206,16 +202,6 @@ gboolean rspamd_parse_host_port_priority (const gchar *str,
rspamd_mempool_t *pool);
/**
- * Parse host:port line
- * @param ina host address
- * @param port port
- * @return TRUE if string was parsed
- */
-gboolean rspamd_parse_host_port (const gchar *str,
- GPtrArray **addrs,
- gchar **name, guint default_port, rspamd_mempool_t *pool);
-
-/**
* Destroy the specified IP address
* @param addr
*/
span>.Status(404) } else { ctx.Error(500, "LookupRepoRedirect", err) } } else { ctx.Error(500, "GetRepositoryByName", err) } return } repo.Owner = owner if ctx.IsSigned && ctx.User.IsAdmin { ctx.Repo.AccessMode = models.AccessModeOwner } else { mode, err := models.AccessLevel(ctx.User, repo) if err != nil { ctx.Error(500, "AccessLevel", err) return } ctx.Repo.AccessMode = mode } if !ctx.Repo.HasAccess() { ctx.Status(404) return } ctx.Repo.Repository = repo } } // Contexter middleware already checks token for user sign in process. func reqToken() macaron.Handler { return func(ctx *context.Context) { if !ctx.IsSigned { ctx.Error(401) return } } } func reqBasicAuth() macaron.Handler { return func(ctx *context.Context) { if !ctx.IsBasicAuth { ctx.Error(401) return } } } func reqAdmin() macaron.Handler { return func(ctx *context.Context) { if !ctx.IsSigned || !ctx.User.IsAdmin { ctx.Error(403) return } } } func reqRepoWriter() macaron.Handler { return func(ctx *context.Context) { if !ctx.Repo.IsWriter() { ctx.Error(403) return } } } func reqOrgMembership() macaron.Handler { return func(ctx *context.APIContext) { var orgID int64 if ctx.Org.Organization != nil { orgID = ctx.Org.Organization.ID } else if ctx.Org.Team != nil { orgID = ctx.Org.Team.OrgID } else { ctx.Error(500, "", "reqOrgMembership: unprepared context") return } if !models.IsOrganizationMember(orgID, ctx.User.ID) { if ctx.Org.Organization != nil { ctx.Error(403, "", "Must be an organization member") } else { ctx.Status(404) } return } } } func reqOrgOwnership() macaron.Handler { return func(ctx *context.APIContext) { var orgID int64 if ctx.Org.Organization != nil { orgID = ctx.Org.Organization.ID } else if ctx.Org.Team != nil { orgID = ctx.Org.Team.OrgID } else { ctx.Error(500, "", "reqOrgOwnership: unprepared context") return } if !models.IsOrganizationOwner(orgID, ctx.User.ID) { if ctx.Org.Organization != nil { ctx.Error(403, "", "Must be an organization owner") } else { ctx.Status(404) } return } } } func orgAssignment(args ...bool) macaron.Handler { var ( assignOrg bool assignTeam bool ) if len(args) > 0 { assignOrg = args[0] } if len(args) > 1 { assignTeam = args[1] } return func(ctx *context.APIContext) { ctx.Org = new(context.APIOrganization) var err error if assignOrg { ctx.Org.Organization, err = models.GetUserByName(ctx.Params(":orgname")) if err != nil { if models.IsErrUserNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetUserByName", err) } return } } if assignTeam { ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid")) if err != nil { if models.IsErrUserNotExist(err) { ctx.Status(404) } else { ctx.Error(500, "GetTeamById", err) } return } } } } func mustEnableIssues(ctx *context.APIContext) { if !ctx.Repo.Repository.EnableUnit(models.UnitTypeIssues) { ctx.Status(404) return } } func mustAllowPulls(ctx *context.Context) { if !ctx.Repo.Repository.AllowsPulls() { ctx.Status(404) return } } // RegisterRoutes registers all v1 APIs routes to web application. // FIXME: custom form error response func RegisterRoutes(m *macaron.Macaron) { bind := binding.Bind m.Group("/v1", func() { // Miscellaneous m.Get("/version", misc.Version) m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) m.Post("/markdown/raw", misc.MarkdownRaw) // Users m.Group("/users", func() { m.Get("/search", user.Search) m.Group("/:username", func() { m.Get("", user.GetInfo) m.Get("/repos", user.ListUserRepos) m.Group("/tokens", func() { m.Combo("").Get(user.ListAccessTokens). Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) }, reqBasicAuth()) }) }) m.Group("/users", func() { m.Group("/:username", func() { m.Get("/keys", user.ListPublicKeys) m.Get("/followers", user.ListFollowers) m.Group("/following", func() { m.Get("", user.ListFollowing) m.Get("/:target", user.CheckFollowing) }) m.Get("/starred", user.GetStarredRepos) m.Get("/subscriptions", user.GetWatchedRepos) }) }, reqToken()) m.Group("/user", func() { m.Get("", user.GetAuthenticatedUser) m.Combo("/emails").Get(user.ListEmails). Post(bind(api.CreateEmailOption{}), user.AddEmail). Delete(bind(api.CreateEmailOption{}), user.DeleteEmail) m.Get("/followers", user.ListMyFollowers) m.Group("/following", func() { m.Get("", user.ListMyFollowing) m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow) }) m.Group("/keys", func() { m.Combo("").Get(user.ListMyPublicKeys). Post(bind(api.CreateKeyOption{}), user.CreatePublicKey) m.Combo("/:id").Get(user.GetPublicKey). Delete(user.DeletePublicKey) }) m.Combo("/repos").Get(user.ListMyRepos). Post(bind(api.CreateRepoOption{}), repo.Create) m.Group("/starred", func() { m.Get("", user.GetMyStarredRepos) m.Group("/:username/:reponame", func() { m.Get("", user.IsStarring) m.Put("", user.Star) m.Delete("", user.Unstar) }, repoAssignment()) }) m.Get("/subscriptions", user.GetMyWatchedRepos) }, reqToken()) // Repositories m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo) m.Group("/repos", func() { m.Get("/search", repo.Search) }) m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID) m.Group("/repos", func() { m.Post("/migrate", bind(auth.MigrateRepoForm{}), repo.Migrate) m.Group("/:username/:reponame", func() { m.Combo("").Get(repo.Get).Delete(repo.Delete) m.Group("/hooks", func() { m.Combo("").Get(repo.ListHooks). Post(bind(api.CreateHookOption{}), repo.CreateHook) m.Combo("/:id").Get(repo.GetHook). Patch(bind(api.EditHookOption{}), repo.EditHook). Delete(repo.DeleteHook) }, reqRepoWriter()) m.Group("/collaborators", func() { m.Get("", repo.ListCollaborators) m.Combo("/:collaborator").Get(repo.IsCollaborator). Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator). Delete(repo.DeleteCollaborator) }) m.Get("/raw/*", context.RepoRef(), repo.GetRawFile) m.Get("/archive/*", repo.GetArchive) m.Combo("/forks").Get(repo.ListForks). Post(bind(api.CreateForkOption{}), repo.CreateFork) m.Group("/branches", func() { m.Get("", repo.ListBranches) m.Get("/:branchname", repo.GetBranch) }) m.Group("/keys", func() { m.Combo("").Get(repo.ListDeployKeys). Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) m.Combo("/:id").Get(repo.GetDeployKey). Delete(repo.DeleteDeploykey) }) m.Group("/issues", func() { m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue) m.Group("/comments", func() { m.Get("", repo.ListRepoIssueComments) m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment) }) m.Group("/:index", func() { m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue) m.Group("/comments", func() { m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment) m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment). Delete(repo.DeleteIssueComment) }) m.Group("/labels", func() { m.Combo("").Get(repo.ListIssueLabels). Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels). Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels). Delete(repo.ClearIssueLabels) m.Delete("/:id", repo.DeleteIssueLabel) }) }) }, mustEnableIssues) m.Group("/labels", func() { m.Combo("").Get(repo.ListLabels). Post(bind(api.CreateLabelOption{}), repo.CreateLabel) m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel). Delete(repo.DeleteLabel) }) m.Group("/milestones", func() { m.Combo("").Get(repo.ListMilestones). Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) m.Combo("/:id").Get(repo.GetMilestone). Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone). Delete(reqRepoWriter(), repo.DeleteMilestone) }) m.Get("/stargazers", repo.ListStargazers) m.Get("/subscribers", repo.ListSubscribers) m.Group("/subscription", func() { m.Get("", user.IsWatching) m.Put("", user.Watch) m.Delete("", user.Unwatch) }) m.Group("/releases", func() { m.Combo("").Get(repo.ListReleases). Post(bind(api.CreateReleaseOption{}), repo.CreateRelease) m.Combo("/:id").Get(repo.GetRelease). Patch(bind(api.EditReleaseOption{}), repo.EditRelease). Delete(repo.DeleteRelease) }) m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) m.Group("/pulls", func() { m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).Post(reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) m.Group("/:index", func() { m.Combo("").Get(repo.GetPullRequest).Patch(reqRepoWriter(), bind(api.EditPullRequestOption{}), repo.EditPullRequest) m.Combo("/merge").Get(repo.IsPullRequestMerged).Post(reqRepoWriter(), repo.MergePullRequest) }) }, mustAllowPulls, context.ReferencesGitRepo()) }, repoAssignment()) }, reqToken()) // Organizations m.Get("/user/orgs", reqToken(), org.ListMyOrgs) m.Get("/users/:username/orgs", org.ListUserOrgs) m.Group("/orgs/:orgname", func() { m.Combo("").Get(org.Get). Patch(reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit) m.Group("/members", func() { m.Get("", org.ListMembers) m.Combo("/:username").Get(org.IsMember). Delete(reqOrgOwnership(), org.DeleteMember) }) m.Group("/public_members", func() { m.Get("", org.ListPublicMembers) m.Combo("/:username").Get(org.IsPublicMember). Put(reqOrgMembership(), org.PublicizeMember). Delete(reqOrgMembership(), org.ConcealMember) }) m.Combo("/teams", reqOrgMembership()).Get(org.ListTeams). Post(bind(api.CreateTeamOption{}), org.CreateTeam) m.Group("/hooks", func() { m.Combo("").Get(org.ListHooks). Post(bind(api.CreateHookOption{}), org.CreateHook) m.Combo("/:id").Get(org.GetHook). Patch(reqOrgOwnership(), bind(api.EditHookOption{}), org.EditHook). Delete(reqOrgOwnership(), org.DeleteHook) }, reqOrgMembership()) }, orgAssignment(true)) m.Group("/teams/:teamid", func() { m.Combo("").Get(org.GetTeam). Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam). Delete(reqOrgOwnership(), org.DeleteTeam) m.Group("/members", func() { m.Get("", org.GetTeamMembers) m.Combo("/:username"). Put(reqOrgOwnership(), org.AddTeamMember). Delete(reqOrgOwnership(), org.RemoveTeamMember) }) m.Group("/repos", func() { m.Get("", org.GetTeamRepos) m.Combo(":orgname/:reponame"). Put(org.AddTeamRepository). Delete(org.RemoveTeamRepository) }) }, reqOrgMembership(), orgAssignment(false, true)) m.Any("/*", func(ctx *context.Context) { ctx.Error(404) }) m.Group("/admin", func() { m.Group("/users", func() { m.Post("", bind(api.CreateUserOption{}), admin.CreateUser) m.Group("/:username", func() { m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser). Delete(admin.DeleteUser) m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey) m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) }) }) }, reqAdmin()) }, context.APIContexter()) }