/* ui-ci.c: redirect to an external CI system * * Copyright (C) 2006-2025 cgit Development Team * * 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); }