aboutsummaryrefslogtreecommitdiff
path: root/ui-ci.c
diff options
from:
to:
context:
space:
mode:
Diffstat (limited to 'ui-ci.c')
-rw-r--r--ui-ci.c129
1 files changed, 129 insertions, 0 deletions
diff --git a/ui-ci.c b/ui-ci.c
new file mode 100644
index 0000000..1c9f39c
--- /dev/null
+++ b/ui-ci.c
@@ -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);
+}