Download raw body.
gotwebd: rss feeds for repo tags
On Mon, Dec 19, 2022 at 12:45:28PM +0100, Omar Polo wrote:
> Hello,
>
> I'm subscribed to several rss feeds (for e.g. release feeds on github)
> and I think it would be nice if gotwebd could provide an RSS feed for
> the tags.
>
> Basically, it's just an alternate way of rendering the tags page.
>
> To do so I had to define an helper function
> `gotweb_render_absolute_url' since the RSS spec[0] as far as I can
> tell wants absolute URLs in some places. In doing so I discovered
> that my idea of using SCRIPT_NAME for the "prefix" doesn't really
> work, with the configuration per the manpage httpd sends an empty
> string as SCRIPT_NAME. DOCUMENT_URI is much better, so switch to it
> too. I also needed to know whether we're over https or plain http.
>
> (will land this change as separate commit if ok)
>
> I've tested it locally where I'm running gotwebd under a path prefix
> and on my server[1], seems to work just fine. I've tested the feeds
> with rssgoemail, liferea, and a validator[2], and all are happy.
>
> What do you think?
>
I don't have a problem with it. The more functionality, the better,
methinks.
> [0]: https://www.rssboard.org/rss-specification
> [1]: https://git.omarpolo.com/
> [2]: https://www.rssboard.org/rss-validator/check.cgi
>
> diff /home/op/w/got
> commit - b2b1792329f5adf1e755614e710d49388845293e
> path + /home/op/w/got
> blob - b4f85fc2121de39b9a1c7ff2f9fcab75f5b7da42
> file + gotwebd/fcgi.c
> --- gotwebd/fcgi.c
> +++ gotwebd/fcgi.c
> @@ -261,12 +261,12 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req
> *sd = '\0';
> }
>
> - if (c->script_name[0] == '\0' &&
> - val_len < MAX_SCRIPT_NAME &&
> - name_len == 11 &&
> - strncmp(buf, "SCRIPT_NAME", 11) == 0) {
> - memcpy(c->script_name, val, val_len);
> - c->script_name[val_len] = '\0';
> + if (c->document_uri[0] == '\0' &&
> + val_len < MAX_DOCUMENT_URI &&
> + name_len == 12 &&
> + strncmp(buf, "DOCUMENT_URI", 12) == 0) {
> + memcpy(c->document_uri, val, val_len);
> + c->document_uri[val_len] = '\0';
> }
>
> if (c->server_name[0] == '\0' &&
> @@ -277,6 +277,10 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req
> c->server_name[val_len] = '\0';
> }
>
> + if (name_len == 5 &&
> + strncmp(buf, "HTTPS", 5) == 0)
> + c->https = 1;
> +
> buf += name_len + val_len;
> n -= name_len - val_len;
> }
> blob - 50fee3056b1e5f7a9bc78f255201d8b1539a2995
> file + gotwebd/got_operations.c
> --- gotwebd/got_operations.c
> +++ gotwebd/got_operations.c
> @@ -572,7 +572,7 @@ got_get_repo_tags(struct request *c, int limit)
> repo_dir->name) == -1)
> return got_error_from_errno("asprintf");
>
> - if (qs->commit == NULL && qs->action == TAGS) {
> + if (qs->commit == NULL && (qs->action == TAGS || qs->action == RSS)) {
> error = got_ref_open(&ref, repo, qs->headref, 0);
> if (error)
> goto err;
> blob - 28042467bba28194c9543f1b05ca9cfc5cfd644e
> file + gotwebd/gotweb.c
> --- gotwebd/gotweb.c
> +++ gotwebd/gotweb.c
> @@ -50,6 +50,7 @@
>
> #include "proc.h"
> #include "gotwebd.h"
> +#include "tmpl.h"
>
> static const struct querystring_keys querystring_keys[] = {
> { "action", ACTION },
> @@ -74,6 +75,7 @@ static const struct action_keys action_keys[] = {
> { "tag", TAG },
> { "tags", TAGS },
> { "tree", TREE },
> + { "rss", RSS },
> };
>
> static const struct got_error *gotweb_init_querystring(struct querystring **);
> @@ -177,6 +179,24 @@ render:
> goto done;
> }
>
> + if (qs->action == RSS) {
> + error = gotweb_render_content_type(c,
> + "application/rss+xml;charset=utf-8");
> + if (error) {
> + log_warnx("%s: %s", __func__, error->msg);
> + goto err;
> + }
> +
> + error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
> + if (error) {
> + log_warnx("%s: %s", __func__, error->msg);
> + goto err;
> + }
> + if (gotweb_render_rss(c->tp) == -1)
> + goto err;
> + goto done;
> + }
> +
> render:
> error = gotweb_render_content_type(c, "text/html");
> if (error) {
> @@ -1624,6 +1644,8 @@ gotweb_action_name(int action)
> return "tags";
> case TREE:
> return "tree";
> + case RSS:
> + return "rss";
> default:
> return NULL;
> }
> @@ -1722,6 +1744,21 @@ gotweb_link(struct request *c, struct gotweb_url *url,
> }
>
> int
> +gotweb_render_absolute_url(struct request *c, struct gotweb_url *url)
> +{
> + struct template *tp = c->tp;
> + const char *proto = c->https ? "http" : "http";
Missing an s here
> +
> + if (fcgi_puts(tp, proto) == -1 ||
> + fcgi_puts(tp, "://") == -1 ||
> + tp_htmlescape(tp, c->server_name) == -1 ||
> + tp_htmlescape(tp, c->document_uri) == -1)
> + return -1;
> +
> + return gotweb_render_url(c, url);
> +}
> +
> +int
> gotweb_link(struct request *c, struct gotweb_url *url, const char *fmt, ...)
> {
> va_list ap;
> @@ -2004,7 +2041,8 @@ gotweb_get_time_str(char **repo_age, time_t committer_
> const char *hours = "hours ago", *minutes = "minutes ago";
> const char *seconds = "seconds ago", *now = "right now";
> char *s;
> - char datebuf[29];
> + char datebuf[64];
> + size_t r;
>
> *repo_age = NULL;
>
> @@ -2056,6 +2094,19 @@ gotweb_get_time_str(char **repo_age, time_t committer_
> if (asprintf(repo_age, "%s UTC", datebuf) == -1)
> return got_error_from_errno("asprintf");
> break;
> + case TM_RFC822:
> + if (gmtime_r(&committer_time, &tm) == NULL)
> + return got_error_from_errno("gmtime_r");
> +
> + r = strftime(datebuf, sizeof(datebuf),
> + "%a, %d %b %Y %H:%M:%S GMT", &tm);
> + if (r == 0)
> + return got_error(GOT_ERR_NO_SPACE);
> +
> + *repo_age = strdup(datebuf);
> + if (*repo_age == NULL)
> + return got_error_from_errno("asprintf");
> + break;
> }
> return NULL;
> }
> blob - fc3b18bef37416ee4320885e46d8707a3291f6c2
> file + gotwebd/gotwebd.h
> --- gotwebd/gotwebd.h
> +++ gotwebd/gotwebd.h
> @@ -52,7 +52,7 @@
>
> /* GOTWEB DEFAULTS */
> #define MAX_QUERYSTRING 2048
> -#define MAX_SCRIPT_NAME 255
> +#define MAX_DOCUMENT_URI 255
> #define MAX_SERVER_NAME 255
>
> #define GOTWEB_GIT_DIR ".git"
> @@ -224,8 +224,9 @@ struct request {
>
> char querystring[MAX_QUERYSTRING];
> char http_host[GOTWEBD_MAXTEXT];
> - char script_name[MAX_SCRIPT_NAME];
> + char document_uri[MAX_DOCUMENT_URI];
> char server_name[MAX_SERVER_NAME];
> + int https;
>
> uint8_t request_started;
> };
> @@ -412,12 +413,14 @@ enum query_actions {
> TAG,
> TAGS,
> TREE,
> + RSS,
> ACTIONS__MAX,
> };
>
> enum gotweb_ref_tm {
> TM_DIFF,
> TM_LONG,
> + TM_RFC822,
> };
>
> extern struct gotwebd *gotwebd_env;
> @@ -441,6 +444,7 @@ int gotweb_link(struct request *, struct gotweb_url *,
> const struct got_error *gotweb_escape_html(char **, const char *);
> const char *gotweb_action_name(int);
> int gotweb_render_url(struct request *, struct gotweb_url *);
> +int gotweb_render_absolute_url(struct request *, struct gotweb_url *);
> int gotweb_link(struct request *, struct gotweb_url *, const char *, ...)
> __attribute__((__format__(printf, 3, 4)))
> __attribute__((__nonnull__(3)));
> @@ -457,6 +461,7 @@ int gotweb_render_commits(struct template *);
> int gotweb_render_briefs(struct template *);
> int gotweb_render_navs(struct template *);
> int gotweb_render_commits(struct template *);
> +int gotweb_render_rss(struct template *);
>
> /* parse.y */
> int parse_config(const char *, struct gotwebd *);
> blob - 6aca4b90f64838bba5e10738d6af4e2a01509ebf
> file + gotwebd/pages.tmpl
> --- gotwebd/pages.tmpl
> +++ gotwebd/pages.tmpl
> @@ -19,6 +19,7 @@
> #include <sys/types.h>
> #include <sys/queue.h>
>
> +#include <ctype.h>
> #include <event.h>
> #include <stdint.h>
> #include <stdlib.h>
> @@ -30,6 +31,9 @@ static int
> #include "gotwebd.h"
> #include "tmpl.h"
>
> +static inline int rss_tag_item(struct template *, struct repo_tag *);
> +static inline int rss_author(struct template *, char *);
> +
> static int
> gotweb_render_age(struct template *tp, time_t time, int ref_tm)
> {
> @@ -53,7 +57,7 @@ gotweb_render_age(struct template *tp, time_t time, in
> struct server *srv = c->srv;
> struct querystring *qs = c->t->qs;
> struct gotweb_url u_path;
> - const char *prfx = c->script_name;
> + const char *prfx = c->document_uri;
> const char *css = srv->custom_css;
>
> memset(&u_path, 0, sizeof(u_path));
> @@ -179,6 +183,11 @@ gotweb_render_age(struct template *tp, time_t time, in
> .index_page = -1,
> .page = -1,
> .path = repo_dir->name,
> + }, rss = {
> + .action = RSS,
> + .index_page = -1,
> + .page = -1,
> + .path = repo_dir->name,
> };
> !}
> <div class="index_wrapper">
> @@ -211,6 +220,8 @@ gotweb_render_age(struct template *tp, time_t time, in
> <a href="{{ render gotweb_render_url(tp->tp_arg, &tags) }}">tags</a>
> {{ " | " }}
> <a href="{{ render gotweb_render_url(tp->tp_arg, &tree) }}">tree</a>
> + {{ " | " }}
> + <a href="{{ render gotweb_render_url(tp->tp_arg, &rss) }}">rss</a>
> </div>
> <div class="dotted_line"></div>
> </div>
> @@ -389,3 +400,98 @@ gotweb_render_age(struct template *tp, time_t time, in
> {{ end }}
> </div>
> {{ end }}
> +
> +{{ define gotweb_render_rss(struct template *tp) }}
> +{!
> + struct request *c = tp->tp_arg;
> + struct server *srv = c->srv;
> + struct transport *t = c->t;
> + struct repo_dir *repo_dir = t->repo_dir;
> + struct repo_tag *rt;
> + struct gotweb_url summary = {
> + .action = SUMMARY,
> + .index_page = -1,
> + .page = -1,
> + .path = repo_dir->name,
> + };
> +!}
> +<?xml version="1.0" encoding="UTF-8"?>
> +<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
> + <channel>
> + <title>Tags of {{ repo_dir->name }}</title>
> + <link>
> + <![CDATA[
> + {{ render gotweb_render_absolute_url(c, &summary) }}
> + ]]>
> + </link>
> + {{ if srv->show_repo_description }}
> + <description>{{ repo_dir->description }}</description>
> + {{ end }}
> + {{ tailq-foreach rt &t->repo_tags entry }}
> + {{ render rss_tag_item(tp, rt) }}
> + {{ end }}
> + </channel>
> +</rss>
> +{{ end }}
> +
> +{{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }}
> +{!
> + struct request *c = tp->tp_arg;
> + struct transport *t = c->t;
> + struct repo_dir *repo_dir = t->repo_dir;
> + char *tag_name = rt->tag_name;
> + struct gotweb_url tag = {
> + .action = TAG,
> + .index_page = -1,
> + .page = -1,
> + .path = repo_dir->name,
> + .commit = rt->commit_id,
> + };
> +
> + if (strncmp(tag_name, "refs/tags/", 10) == 0)
> + tag_name += 10;
> +!}
> +<item>
> + <title>{{ repo_dir->name }} {{" "}} {{ tag_name }}</title>
> + <link>
> + <![CDATA[
> + {{ render gotweb_render_absolute_url(c, &tag) }}
> + ]]>
> + </link>
> + <description>
> + <![CDATA[<pre>{{ rt->tag_commit }}</pre>]]>
> + </description>
> + {{ render rss_author(tp, rt->tagger) }}
> + <guid isPermaLink="false">{{ rt->commit_id }}</guid>
> + <pubDate>
> + {{ render gotweb_render_age(tp, rt->tagger_time, TM_RFC822) }}
> + </pubDate>
> +</item>
> +{{ end }}
> +
> +{{ define rss_author(struct template *tp, char *author) }}
> +{!
> + char *t, *mail;
> +
> + /* what to do if the author name contains a paren? */
> + if (strchr(author, '(') != NULL || strchr(author, ')') != NULL)
> + return 0;
> +
> + t = strchr(author, '<');
> + if (t == NULL)
> + return 0;
> + *t = '\0';
> + mail = t+1;
> +
> + while (isspace((unsigned char)*--t))
> + *t = '\0';
> +
> + t = strchr(mail, '>');
> + if (t == NULL)
> + return 0;
> + *t = '\0';
> +!}
> +<author>
> + {{ mail }} {{" "}} ({{ author }})
> +</author>
> +{{ end }}
>
--
Tracey Emery
gotwebd: rss feeds for repo tags