aboutsummaryrefslogtreecommitdiff
path: root/ui-ci.c
blob: 1c9f39cebb1fd09747e438e96a3c39574894b5d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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);
}