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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
/* ui-ci.c: redirect to an external CI system
*
* Copyright (C) 2026 Saya Andy <saya.andy@posteo.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);
}
/* Ask the ci filter whether the pipeline denoted by url actually exists.
* The filter must not write to stdout, and signals "yes" by exiting with
* a zero status.
*/
static int ci_filter_accepts(const char *refkind, const char *url)
{
struct cgit_filter *filter = ctx.repo->ci_filter;
if (!filter)
return 1;
if (cgit_open_filter(filter, ctx.qry.head, refkind, url))
return 0;
return !cgit_close_filter(filter);
}
static void resolve_ci_url(void)
{
const char *template, *refkind;
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)) {
refkind = "tag";
template = ctx.repo->ci_tag_url;
} else {
refkind = "branch";
template = ctx.repo->ci_branch_url;
}
if (!template)
template = ctx.repo->ci_url;
if (!template)
return;
ci_url = expand_ci_url(template);
if (!ci_filter_accepts(refkind, ci_url)) {
free(ci_url);
ci_url = NULL;
return;
}
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);
}
|