"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Tracey Emery <tracey@traceyemery.net>
Subject:
Re: gotwebd: rss feeds for repo tags
To:
Omar Polo <op@omarpolo.com>
Cc:
gameoftrees@openbsd.org
Date:
Mon, 19 Dec 2022 13:32:59 -0700

Download raw body.

Thread
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