diff options
| author | 2026-07-30 12:26:52 +0700 | |
|---|---|---|
| committer | 2026-07-30 12:26:52 +0700 | |
| commit | b4b4f2325db71041f38902143b28bae94762b111 (patch) | |
| tree | 8ad6f71551b249d7aa52fc864d586becd4ff34de /ui-ci.c | |
| parent | dbed2ecdf824d154f7d69a624013a02e6e0966be (diff) | |
| download | cgitext-b4b4f2325db71041f38902143b28bae94762b111.tar.gz cgitext-b4b4f2325db71041f38902143b28bae94762b111.zip | |
ui-ci: add a "ci" tab which redirects to an external ci system
Add a "ci" page which redirects to an external ci system for the ref
being viewed, along with a tab for it in the repository header.
The target is a url template, configurable globally or per repository,
with separate settings for branches and tags because pipelines for the
two commonly live at different locations: on a Jenkins multibranch
pipeline a branch is at job/<name>/job/<ref> while a tag is at
job/<name>/view/tags/job/<ref>. $ref, $repo and $slug are substituted,
$slug being the repository url with any ".git" suffix removed and slashes
replaced by dashes, which is the shape job names usually take.
An explicit repo.ci-url drops the branch and tag urls the repository
would otherwise inherit from the global settings, as it could never take
effect otherwise.
The expansion is written to the Location header verbatim rather than
through cgit_redirect(), which percent-encodes '?', '=' and '%' and would
corrupt any url carrying a query string. An expansion containing CR or LF
is refused so that it cannot smuggle in further headers.
Signed-off-by: Saya Andy <saya.andy@posteo.com>
Diffstat (limited to 'ui-ci.c')
| -rw-r--r-- | ui-ci.c | 129 |
1 files changed, 129 insertions, 0 deletions
@@ -0,0 +1,129 @@ +/* ui-ci.c: redirect to an external CI system + * + * Copyright (C) 2006-2025 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#define USE_THE_REPOSITORY_VARIABLE + +#include "cgit.h" +#include "ui-ci.h" +#include "html.h" +#include "ui-shared.h" + +/* Memoized outcome of resolving the ci url for this request: -1 while + * unresolved, 0 when there is no ci url for the current ref, and 1 when + * ci_url holds the url to redirect to. + */ +static int ci_resolved = -1; +static char *ci_url; + +static int ref_is_tag(const char *ref) +{ + struct strbuf fullref = STRBUF_INIT; + struct object_id oid; + int is_tag; + + strbuf_addf(&fullref, "refs/tags/%s", ref); + is_tag = !repo_get_oid(the_repository, fullref.buf, &oid); + strbuf_release(&fullref); + return is_tag; +} + +/* Append the repository url with any ".git" suffix removed and all + * slashes replaced by dashes, e.g. "sayauz/web.git" becomes + * "sayauz-web". + */ +static void add_repo_slug(struct strbuf *buf, const char *url) +{ + size_t len, i; + + if (!strip_suffix(url, ".git", &len)) + len = strlen(url); + for (i = 0; i < len; i++) + strbuf_addch(buf, url[i] == '/' ? '-' : url[i]); +} + +static char *expand_ci_url(const char *template) +{ + const char *p, *rest; + struct strbuf buf = STRBUF_INIT; + + for (p = template; *p; p++) { + if (*p != '$') { + strbuf_addch(&buf, *p); + continue; + } + if (skip_prefix(p, "$ref", &rest)) + strbuf_addstr(&buf, ctx.qry.head); + else if (skip_prefix(p, "$repo", &rest)) + strbuf_addstr(&buf, ctx.repo->url); + else if (skip_prefix(p, "$slug", &rest)) + add_repo_slug(&buf, ctx.repo->url); + else if (skip_prefix(p, "$$", &rest)) + strbuf_addch(&buf, '$'); + else { + strbuf_addch(&buf, '$'); + continue; + } + p = rest - 1; + } + return strbuf_detach(&buf, NULL); +} + +static void resolve_ci_url(void) +{ + const char *template; + + if (ci_resolved != -1) + return; + ci_resolved = 0; + + if (!ctx.repo || !ctx.qry.head || !cgit_have_repository()) + return; + + if (ref_is_tag(ctx.qry.head)) + template = ctx.repo->ci_tag_url; + else + template = ctx.repo->ci_branch_url; + if (!template) + template = ctx.repo->ci_url; + if (!template) + return; + + ci_url = expand_ci_url(template); + ci_resolved = 1; +} + +int cgit_ci_available(void) +{ + resolve_ci_url(); + return ci_resolved == 1; +} + +void cgit_print_ci(void) +{ + resolve_ci_url(); + + if (ci_resolved != 1) { + cgit_print_error_page(404, "Not found", + "No ci url available for %s", + ctx.qry.head); + return; + } + + /* The expanded template is emitted verbatim rather than through + * cgit_redirect(), which percent-encodes characters such as '?', + * '=' and '%' that are meaningful in a ci url. Refuse anything + * which could be used to smuggle in extra response headers. + */ + if (strpbrk(ci_url, "\r\n")) { + cgit_print_error_page(500, "Internal server error", + "Malformed ci url"); + return; + } + + htmlf("Status: 302 Found\nLocation: %s\n\n", ci_url); +} |