{!
/*
 * Copyright (c) 2022 Omar Polo <op@openbsd.org>
 * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "got_compat.h"

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/stat.h>

#include <ctype.h>
#include <event.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <imsg.h>

#include "got_error.h"
#include "got_object.h"
#include "got_reference.h"
#include "got_path.h"

#include "media.h"
#include "gotwebd.h"
#include "log.h"
#include "tmpl.h"

enum gotweb_ref_tm {
	TM_DIFF,
	TM_LONG,
};

static int breadcumbs(struct template *);
static int datetime(struct template *, time_t, int);
static int gotweb_render_blob_line(struct template *, const char *, size_t);
static int gotweb_render_tree_item(struct template *, struct got_tree_entry *);
static int blame_line(struct template *, const char *, struct blame_line *,
    int, int);

static inline int gotweb_render_more(struct template *, int);

static inline int tree_listing(struct template *);
static inline int diff_line(struct template *, char *);
static inline int tag_item(struct template *, struct repo_tag *);
static inline int branch(struct template *, struct got_reflist_entry *);
static inline int rss_tag_item(struct template *, struct repo_tag *);
static inline int rss_author(struct template *, char *);

static inline char *
nextsep(char *s, char **t)
{
	char *q;

	while (*s == '/')
		s++;
	*t = s;
	if (*s == '\0')
		return NULL;

	q = strchr(s, '/');
	if (q == NULL)
		q = strchr(s, '\0');
	return q;
}

!}

{{ define datetime(struct template *tp, time_t t, int fmt) }}
{!
	struct tm	 tm;
	char		 rfc3339[64];
	char		 datebuf[64];

	if (gmtime_r(&t, &tm) == NULL)
		return -1;

	if (strftime(rfc3339, sizeof(rfc3339), "%FT%TZ", &tm) == 0)
		return -1;

	if (fmt != TM_DIFF && asctime_r(&tm, datebuf) == NULL)
		return -1;
!}
<time datetime="{{ rfc3339 }}">
  {{ if fmt == TM_DIFF }}
    {{ render gotweb_render_age(tp, t) }}
  {{ else }}
    {{ datebuf }} {{ " UTC" }}
  {{ end }}
</time>
{{ end }}

{{ define breadcumbs(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	const struct querystring *qs = c->t->qs;
	struct gotweb_url	 url;
	const char		*folder = qs->folder;
	const char		*action = "tree";
	char			*t, *s = NULL, *dir = NULL;
	char			 ch;

	memset(&url, 0, sizeof(url));
	url.index_page = -1;
	url.action = TREE;
	url.path = qs->path[0] ? qs->path : NULL;
	url.commit = qs->commit[0] ? qs->commit : NULL;

	if (qs->action != TREE && qs->action != BLOB) {
		action = gotweb_action_name(qs->action);
		url.action = qs->action;
	}

	if (*folder != '\0') {
		while (*folder == '/')
			folder++;
		dir = strdup(folder);
		if (dir == NULL)
			return (-1);
		s = dir;
	}
!}
  {{ " / " }}
  <a href="{{ render gotweb_render_url(c, &url) }}">{{ action }}</a>
  {{ " / " }}
  {{ if dir }}
    {{ while (s = nextsep(s, &t)) != NULL }}
      {!
	ch = *s;
	*s = '\0';
	url.folder = dir;
      !}

      <a href="{{ render gotweb_render_url(c, &url) }}">
        {{ t }}
      </a>
      {{ " / " }}

      {! *s = ch; !}
    {{ end }}
  {{ end }}

  {{ if qs->file[0] }}
    {{ qs->file }}
  {{ end}}

  {{ finally }}
  {! free(dir); !}
{{ end }}

{{ define gotweb_render_page(struct template *tp,
    int (*body)(struct template *)) }}
{!
	struct request		*c = tp->tp_arg;
	struct server		*srv = c->srv;
	const struct querystring *qs = c->t->qs;
	struct gotweb_url	 u_path, u_logout;
	char			 prefix[MAX_DOCUMENT_URI * 2];
	const char		*css = srv->custom_css;
	const char		*url_root, *url_path;

	strlcpy(prefix, "/", sizeof(prefix));

	url_root = srv->gotweb_url_root;
	while (url_root[0] == '/')
		url_root++;
	if (url_root[0] != '\0')
		strlcat(prefix, url_root, sizeof(prefix));

	url_path = srv->repos_url_path;
	while (url_path[0] == '/')
		url_path++;
	if (url_path[0] != '\0') {
		if (url_root[0] != '\0') {
			got_path_strip_trailing_slashes(prefix);
			strlcat(prefix, "/", sizeof(prefix));
		}
		strlcat(prefix, url_path, sizeof(prefix));
	}

	got_path_strip_trailing_slashes(prefix);

	memset(&u_path, 0, sizeof(u_path));
	u_path.index_page = -1;
	u_path.action = SUMMARY;

	memset(&u_logout, 0, sizeof(u_logout));
	u_logout.index_page = -1;
	u_logout.action = INDEX;
	u_logout.logout = 1;
!}
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>{{ srv->site_name }}</title>
    <meta name="viewport" content="initial-scale=1.0" />
    <meta name="msapplication-TileColor" content="#da532c" />
    <meta name="theme-color" content="#ffffff"/>
    <link rel="apple-touch-icon" sizes="180x180" href="{{ prefix }}/apple-touch-icon.png" />
    <link rel="icon" type="image/png" sizes="32x32" href="{{ prefix }}/favicon-32x32.png" />
    <link rel="icon" type="image/png" sizes="16x16" href="{{ prefix }}/favicon-16x16.png" />
    <link rel="manifest" href="{{ prefix }}/site.webmanifest"/>
    <link rel="mask-icon" href="{{ prefix }}/safari-pinned-tab.svg" />
    <link rel="stylesheet" type="text/css" href="{{ prefix }}/{{ css }}" />
  </head>
  <body>
    <header id="header">
      <div id="got_link">
        <a href="{{ srv->logo_url }}" target="_blank">
          <img src="{{ prefix }}/{{ srv->logo }}" />
        </a>
      </div>
      {{ if (c->client_username[0] || c->access_identifier[0]) }}	
      <div id="login_status">
        Logged in as: <b>{{ c->client_username[0] ?
	    c->client_username : c->access_identifier }}</b> &nbsp;
	(<a href="{{ render gotweb_render_url(c, &u_logout) }}">Logout</a>)
      </div>
      {{ end }}
    </header>
    <nav id="site_path">
      <div id="site_link">
        <a href="?index_page={{ printf "%d", qs->index_page }}">
          {{ srv->site_link }}
        </a>
        {{ if qs->path[0] }}
          {! u_path.path = qs->path[0] ? qs->path : NULL; !}
          {{ " / " }}
          <a href="{{ render gotweb_render_url(tp->tp_arg, &u_path)}}">
            {{ qs->path }}
          </a>
        {{ end }}
        {{ if qs->action == SUMMARY || qs->action == DIFF ||
              qs->action == TAG || qs->action == TAGS }}
          {{ " / " }}{{ gotweb_action_name(qs->action) }}
        {{ else if qs->action != INDEX}}
          {{ render breadcumbs(tp) }}
        {{ end }}
      </div>
    </nav>
    <main class="action-{{ gotweb_action_name(qs->action) }}">
      {{ render body(tp) }}
    </main>
    <footer id="site_owner_wrapper">
      <p id="site_owner">
        {{ if srv->show_site_owner }}
          {{ srv->site_owner }}
        {{ end }}
      </p>
    </footer>
  </body>
</html>
{{ end }}

{{ define gotweb_render_error(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	struct server		*srv = NULL;
	const char		*login_hint_user = NULL;
	const char		*login_hint_port = NULL;

	if (t->error && t->error->code == GOT_ERR_LOGIN_HINT) {
		srv = gotweb_get_server(c->fcgi_params.server_name);
		if (srv && srv->login_hint_user[0] != '\0') {
			login_hint_user = srv->login_hint_user;
			if (srv->login_hint_port[0] != '\0')
				login_hint_port = srv->login_hint_port;
		}
	}
!}
<div id="err_content">
  {{ if t->error }}
    {{ if t->error->code == GOT_ERR_LOGIN_HINT && login_hint_user }}
      <p>Log in by running:</p>
      <pre>
      {{ if login_hint_port }}
	ssh -p {{ login_hint_port }} {{" "}} {{ login_hint_user }}@{{ srv->name }} {{" "}} "weblogin {{ srv->name }}"
	
      {{ else }}
	ssh {{ login_hint_user }}@{{ srv->name }} {{" "}} "weblogin {{ srv->name }}"
      {{ end }}
      </pre>
      {{ if srv->sshfp[GOTWEBD_SSHFP_ECDSA][0] != '\0' ||
          srv->sshfp[GOTWEBD_SSHFP_ED25519][0] != '\0' ||
          srv->sshfp[GOTWEBD_SSHFP_RSA][0] != '\0' }}
	{{ if login_hint_port }}
	  <p>The SSH host key fingerprints of {{ srv->name }}:{{ login_hint_port }} {{" "}} are:</p>
	{{ else }}
	  <p>The SSH host key fingerprints of {{ srv->name }} {{" "}} are:</p>
	{{ end }}
	<dl>
        {{ if srv->sshfp[GOTWEBD_SSHFP_ECDSA][0] != '\0' }}
          <dt>ECDSA</dt><dd><pre class="clone-url">{{ srv->sshfp[GOTWEBD_SSHFP_ECDSA] }}</pre></dd>
        {{ end }}
        {{ if srv->sshfp[GOTWEBD_SSHFP_ED25519][0] != '\0' }}
          <dt>ED25519</dt><dd><pre class="clone-url">{{ srv->sshfp[GOTWEBD_SSHFP_ED25519] }}</pre></dd>
        {{ end }}
        {{ if srv->sshfp[GOTWEBD_SSHFP_RSA][0] != '\0' }}
          <dt>RSA</dt><dd><pre class="clone-url">{{ srv->sshfp[GOTWEBD_SSHFP_RSA] }}</pre></dd>
        {{ end }}
	</dl>
      {{ end }}

    {{ else }}
      {{ t->error->msg }}
    {{ end }}
  {{ else }}
    See daemon logs for details
  {{ end }}
</div>
{{ end }}

{{ define gotweb_render_repo_table_hdr(struct template *tp) }}
{!
	struct request *c = tp->tp_arg;
	struct server *srv = c->srv;
!}
<div id="index_header">
  <div class="index_project">
    Repository
  </div>
  {{ if srv->show_repo_description }}
    <div class="index_project_description">
      Description
    </div>
  {{ end }}
  {{ if srv->show_repo_owner }}
    <div class="index_project_owner">
      Owner
    </div>
  {{ end }}
  {{ if srv->show_repo_age }}
    <div class="index_project_age">
      Last Change
    </div>
  {{ end }}
</div>
{{ end }}

{{ define gotweb_render_repo_fragment(struct template *tp, struct repo_dir *repo_dir) }}
{!
	struct request *c = tp->tp_arg;
	struct server *srv = c->srv;
	struct gotweb_url summary = {
		.action = SUMMARY,
		.index_page = -1,
		.path = repo_dir->name,
	}, briefs = {
		.action = BRIEFS,
		.index_page = -1,
		.path = repo_dir->name,
	}, commits = {
		.action = COMMITS,
		.index_page = -1,
		.path = repo_dir->name,
	}, tags = {
		.action = TAGS,
		.index_page = -1,
		.path = repo_dir->name,
	}, tree = {
		.action = TREE,
		.index_page = -1,
		.path = repo_dir->name,
	}, rss = {
		.action = RSS,
		.index_page = -1,
		.path = repo_dir->name,
	};
!}
<div class="index_wrapper">
  <div class="index_project">
    <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">{{ repo_dir->name }}</a>
  </div>
  {{ if srv->show_repo_description }}
    <div class="index_project_description">
      {{ repo_dir->description }}
    </div>
  {{ end }}
  {{ if srv->show_repo_owner }}
    <div class="index_project_owner">
      {{ repo_dir->owner }}
    </div>
  {{ end }}
  {{ if srv->show_repo_age }}
    <div class="index_project_age">
      {{ render datetime(tp, repo_dir->age, TM_DIFF) }}
    </div>
  {{ end }}
  <div class="navs_wrapper">
    <div class="navs">
      <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">summary</a>
      {{ " | " }}
      <a href="{{ render gotweb_render_url(tp->tp_arg, &briefs) }}">briefs</a>
      {{ " | " }}
      <a href="{{ render gotweb_render_url(tp->tp_arg, &commits) }}">commits</a>
      {{ " | " }}
      <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>
    <hr />
  </div>
</div>
{{ end }}

{{ define gotweb_render_briefs(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	const struct querystring *qs = c->t->qs;
	struct repo_commit	*rc;
	struct repo_dir		*repo_dir = t->repo_dir;
	struct gotweb_url	 diff_url, patch_url, tree_url;
	char			*tmp, *body;

	diff_url = (struct gotweb_url){
		.action = DIFF,
		.index_page = -1,
		.path = repo_dir->name,
		.headref = qs->headref,
	};
	patch_url = (struct gotweb_url){
		.action = PATCH,
		.index_page = -1,
		.path = repo_dir->name,
		.headref = qs->headref,
	};
	tree_url = (struct gotweb_url){
		.action = TREE,
		.index_page = -1,
		.path = repo_dir->name,
		.headref = qs->headref,
	};
!}
<header class='subtitle'>
  <h2>Commit Briefs</h2>
</header>
<div id="briefs_content">
  {{ tailq-foreach rc &t->repo_commits entry }}
    {!
	diff_url.commit = rc->commit_id;
	patch_url.commit = rc->commit_id;
	tree_url.commit = rc->commit_id;

	tmp = strchr(rc->committer, '<');
	if (tmp)
		*tmp = '\0';

	body = strchr(rc->commit_msg, '\n');
	if (body) {
		*body++ = '\0';
		while (*body == '\n')
		      body++;
	}
    !}
    <div class='brief'>
      <p class='brief_meta'>
        <span class='briefs_age'>
          {{ render datetime(tp, rc->committer_time, TM_DIFF) }}
        </span>
        {{" "}}
        <span class='briefs_id'>
          {{ printf "%.10s", rc->commit_id }}
        </span>
        {{" "}}
        <span class="briefs_author">
          {{ rc->committer }}
        </span>
      </p>
      {{ if body && *body != '\0' }}
        <details class="briefs_log">
          <summary>
            <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
              {{ rc->commit_msg }}
            </a>
            {{ if rc->refs_str }}
              {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
            {{ end }}
            {{ " " }}
            <span class="briefs_toggle" aria-hidden="true">
              {{ " ⋅⋅⋅ " }}
            </span>
          </summary>
          {{ "\n" }}
          <p>{{ body }}</p>
        </details>
      {{ else }}
        <p class="briefs_log">
          <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
            {{ rc->commit_msg }}
          </a>
          {{ if rc->refs_str }}
            {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
          {{ end }}
        </p>
      {{ end }}
    </div>
    <div class="navs_wrapper">
      <div class="navs">
        <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">diff</a>
	{{ " | " }}
        <a href="{{ render gotweb_render_url(tp->tp_arg, &patch_url) }}">patch</a>
	{{ " | " }}
	<a href="{{ render gotweb_render_url(tp->tp_arg, &tree_url) }}">tree</a>
      </div>
    </div>
    <hr />
  {{ end }}
  {{ render gotweb_render_more(tp, BRIEFS) }}
</div>
{{ end }}

{{ define gotweb_render_more(struct template *tp, int action) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	const struct querystring *qs = t->qs;
	struct gotweb_url	 more = {
		.action = action,
		.index_page = -1,
		.path = qs->path[0] ? qs->path : NULL,
		.commit = t->more_id,
		.headref = qs->headref,
		.folder = qs->folder[0] ? qs->folder : NULL,
		.file = qs->file[0] ? qs->file : NULL,
	};

	if (action == TAGS)
		more.commit = t->tags_more_id;
!}
  {{ if more.commit }}
    <div id="np_wrapper">
      <div id="nav_more">
        <a href="{{ render gotweb_render_url(c, &more) }}">
          More&nbsp;&darr;
        </a>
      </div>
    </div>
  {{ end }}
{{ end }}

{{ define gotweb_render_navs(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct gotweb_url	 prev, next;
	int			 have_prev, have_next;

	gotweb_index_navs(c, &prev, &have_prev, &next, &have_next);
!}
<div id="np_wrapper">
  <div id="nav_prev">
    {{ if have_prev }}
      <a href="{{ render gotweb_render_url(c, &prev) }}">
        Previous
      </a>
    {{ end }}
  </div>
  <div id="nav_next">
    {{ if have_next }}
      <a href="{{ render gotweb_render_url(c, &next) }}">
        Next
      </a>
    {{ end }}
  </div>
</div>
{{ end }}

{{ define gotweb_render_commits(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	struct repo_dir		*repo_dir = t->repo_dir;
	struct repo_commit	*rc;
	struct gotweb_url	 diff, patch, tree;

	diff = (struct gotweb_url){
		.action = DIFF,
		.index_page = -1,
		.path = repo_dir->name,
	};
	patch = (struct gotweb_url){
		.action = PATCH,
		.index_page = -1,
		.path = repo_dir->name,
	};
	tree = (struct gotweb_url){
		.action = TREE,
		.index_page = -1,
		.path = repo_dir->name,
	};
!}
<header class="subtitle">
  <h2>Commits</h2>
</header>
<div class="commits_content">
  {{ tailq-foreach rc &t->repo_commits entry }}
    {!
	diff.commit = rc->commit_id;
	patch.commit = rc->commit_id;
	tree.commit = rc->commit_id;
    !}
    <div class="page_header_wrapper">
      <dl>
        <dt>Commit:</dt>
        <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
        <dt>From:</dt>
        <dd>{{ rc->author }}</dd>
	{{ if strcmp(rc->committer, rc->author) != 0 }}
          <dt>Via:</dt>
          <dd>{{ rc->committer }}</dd>
	{{ end }}
        <dt>Date:</dt>
        <dd>
          {{ render datetime(tp, rc->committer_time, TM_LONG) }}
        </dd>
      </dl>
    </div>
    <hr />
    <div class="commit">
      {{ "\n" }}
      {{ rc->commit_msg }}
    </div>
    <div class="navs_wrapper">
      <div class="navs">
        <a href="{{ render gotweb_render_url(c, &diff) }}">diff</a>
	{{ " | " }}
        <a href="{{ render gotweb_render_url(c, &patch) }}">patch</a>
	{{ " | " }}
        <a href="{{ render gotweb_render_url(c, &tree) }}">tree</a>
      </div>
    </div>
    <hr />
  {{ end }}
  {{ render gotweb_render_more(tp, COMMITS) }}
</div>
{{ end }}

{{ define gotweb_render_blob(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	const struct querystring *qs = t->qs;
	struct got_blob_object	*blob = t->blob;
	struct repo_commit	*rc = TAILQ_FIRST(&t->repo_commits);
	struct gotweb_url	 briefs_url, blame_url, raw_url;

	memset(&briefs_url, 0, sizeof(briefs_url));
	briefs_url.index_page = -1,
	briefs_url.action = BRIEFS,
	briefs_url.path = qs->path[0] ? qs->path : NULL,
	briefs_url.commit = qs->commit[0] ? qs->commit : NULL,
	briefs_url.folder = qs->folder[0] ? qs->folder : NULL,
	briefs_url.file = qs->file[0] ? qs->file : NULL,

	memcpy(&blame_url, &briefs_url, sizeof(blame_url));
	blame_url.action = BLAME;

	memcpy(&raw_url, &briefs_url, sizeof(raw_url));
	raw_url.action = BLOBRAW;
!}
<header class="subtitle">
  <h2>Blob</h2>
</header>
<div id="blob_content">
  <div class="page_header_wrapper">
    <dl>
      <dt>Date:</dt>
      <dd>
        {{ render datetime(tp, rc->committer_time, TM_LONG) }}
      </dd>
      <dt>Message:</dt>
      <dd class="commit-msg">{{ rc->commit_msg }}</dd>
      <dt>Actions:</dt>
      <dd>
        <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
          History
        </a>
        {{" | "}}
        <a href="{{ render gotweb_render_url(c, &blame_url) }}">
          Blame
        </a>
	{{" | "}}
        <a href="{{ render gotweb_render_url(c, &raw_url) }}">
          Raw File
        </a>
      </dd>
    </dl>
  </div>
  <hr />
  <div id="blob">
    <pre>
      {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
    </pre>
  </div>
</div>
{{ end }}

{{ define gotweb_render_blob_line(struct template *tp, const char *line,
    size_t no) }}
{!
	char		 lineno[16];
	int		 r;

	r = snprintf(lineno, sizeof(lineno), "%zu", no);
	if (r < 0 || (size_t)r >= sizeof(lineno))
		return -1;
!}
<div class="blob_line" id="line{{ lineno }}">
  <a href="#line{{ lineno }}">{{ lineno }}{{" "}}</a>
  <span class="blob_code">{{ line }}</span>
</div>
{{ end }}

{{ define tree_listing(struct template *tp) }}
{!
	const struct got_error	*error;
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	const struct querystring *qs = c->t->qs;
	struct gotweb_url	 url;
	char			*readme = NULL;
	int			 binary;
	const uint8_t		*buf;
	size_t			 len;
!}
  <table id="tree">
    {{ render got_output_repo_tree(c, &readme, gotweb_render_tree_item) }}
  </table>
  {{ if readme }}
    {!
	error = got_open_blob_for_output(&t->blob, &t->fd, &binary, c,
	    qs->folder[0] ? qs->folder : NULL, readme,
	    qs->commit[0] ? qs->commit : NULL);
	if (error) {
		free(readme);
		return (-1);
	}

	memset(&url, 0, sizeof(url));
	url.index_page = -1;
	url.action = BLOB;
	url.path = t->qs->path[0] ? t->qs->path : NULL;
	url.file = readme;
	url.folder = t->qs->folder[0] ? t->qs->folder : "";
	url.commit = t->qs->commit[0] ? t->qs->commit : NULL;
    !}
    {{ if !binary }}
      <h2>
        <a href="{{ render gotweb_render_url(c, &url) }}">
          {{ readme }}
        </a>
      </h2>
      <pre>
        {!
		for (;;) {
			error = got_object_blob_read_block(&len, t->blob);
			if (error) {
				free(readme);
				return (-1);
			}
			if (len == 0)
				break;
			buf = got_object_blob_get_read_buf(t->blob);
			if (tp_write_htmlescape(tp, buf, len) == -1) {
				free(readme);
				return (-1);
			}
		}
        !}
      </pre>
    {{ end }}
  {{ end }}
{{ finally }}
  {! free(readme); !}
{{ end }}

{{ define gotweb_render_tree(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	struct repo_commit	*rc = TAILQ_FIRST(&t->repo_commits);
!}
<header class='subtitle'>
  <h2>Tree</h2>
</header>
<div id="tree_content">
  <div class="page_header_wrapper">
    <dl>
      <dt>Tree:</dt>
      <dd><code class="commit-id">{{ rc->tree_id }}</code></dd>
      <dt>Date:</dt>
      <dd>
        {{ render datetime(tp, rc->committer_time, TM_LONG) }}
      </dd>
      <dt>Message:</dt>
      <dd class="commit-msg">{{ rc->commit_msg }}</dd>
    </dl>
  </div>
  <hr />
  {{ render tree_listing(tp) }}
</div>
{{ end }}

{{ define gotweb_render_tree_item(struct template *tp,
    struct got_tree_entry *te) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	const struct querystring *qs = t->qs;
	struct repo_commit	*rc = TAILQ_FIRST(&t->repo_commits);
	const char		*modestr = "";
	const char		*name;
	const char		*folder;
	char			*dir = NULL;
	mode_t			 mode;
	struct gotweb_url	 url = {
	       .index_page = -1,
	       .commit = rc->commit_id,
	       .path = qs->path[0] ? qs->path : NULL,
	};

	name = got_tree_entry_get_name(te);
	mode = got_tree_entry_get_mode(te);

	folder = qs->folder[0] ? qs->folder : "";
	if (S_ISDIR(mode)) {
		if (asprintf(&dir, "%s/%s", folder, name) == -1)
			return (-1);

		url.action = TREE;
		url.folder = dir;
	} else {
		url.action = BLOB;
		url.folder = folder;
		url.file = name;
	}

	if (got_object_tree_entry_is_submodule(te))
		modestr = "$";
	else if (S_ISLNK(mode))
		modestr = "@";
	else if (S_ISDIR(mode))
		modestr = "/";
	else if (mode & S_IXUSR)
		modestr = "*";
!}
<tr class="tree_wrapper">
  {{ if S_ISDIR(mode) }}
    <td class="tree_line" colspan=2>
      <a href="{{ render gotweb_render_url(c, &url) }}">
        {{ name }}{{ modestr }}
      </a>
    </td>
  {{ else }}
    <td class="tree_line">
      <a href="{{ render gotweb_render_url(c, &url) }}">
        {{ name }}{{ modestr }}
      </a>
    </td>
    <td class="tree_line_blank">
      {! url.action = COMMITS; !}
      <a href="{{ render gotweb_render_url(c, &url) }}">
        commits
      </a>
      {{ " | " }}
      {! url.action = BLAME; !}
      <a href="{{ render gotweb_render_url(c, &url) }}">
        blame
      </a>
    </td>
  {{ end }}
</tr>
{{ finally }}
{!
	free(dir);
!}
{{ end }}

{{ define gotweb_render_tags(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	struct repo_tag		*rt;
!}
<header class='subtitle'>
  <h2>Tags</h2>
</header>
<div id="tags_content">
  {{ if TAILQ_EMPTY(&t->repo_tags) }}
    <div id="err_content">
      This repository contains no tags
    </div>
  {{ else }}
    {{ tailq-foreach rt &t->repo_tags entry }}
      {{ render tag_item(tp, rt) }}
    {{ end }}
    {{ render gotweb_render_more(tp, TAGS) }}
  {{ end }}
</div>
{{ end }}

{{ define 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;
	char			*msg = rt->tag_commit;
	char			*nl;
	struct gotweb_url	 url = {
		.action = TAG,
		.index_page = -1,
		.path = repo_dir->name,
		.commit = rt->commit_id,
	};

	if (strncmp(tag_name, "refs/tags/", 10) == 0)
		tag_name += 10;

	if (msg) {
		nl = strchr(msg, '\n');
		if (nl)
			*nl = '\0';
	}
!}
<div class="tag_age">
  {{ render datetime(tp, rt->tagger_time, TM_DIFF) }}
</div>
<div class="tag_name">{{ tag_name }}</div>
<div class="tag_log">
  <a href="{{ render gotweb_render_url(c, &url) }}">
    {{ msg }}
  </a>
</div>
<div class="navs_wrapper">
  <div class="navs">
    <a href="{{ render gotweb_render_url(c, &url) }}">tag</a>
    {{ " | " }}
    {! url.action = BRIEFS; !}
    <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
    {{ " | " }}
    {! url.action = COMMITS; !}
    <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
  </div>
</div>
<hr />
{{ end }}

{{ define gotweb_render_tag(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	struct repo_tag		*rt;
	const char		*tag_name;

	rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
	tag_name = rt->tag_name;

	if (strncmp(tag_name, "refs/", 5) == 0)
		tag_name += 5;
!}
<header class="subtitle">
  <h2>Tag</h2>
</header>
<div id="tags_content">
  <div class="page_header_wrapper">
    <dl>
      <dt>Commit:</dt>
      <dd>
        <code class="commit-id">{{ rt->commit_id }}</code>
        {{ " " }}
        <span class="refs_str">({{ tag_name }})</span>
      </dd>
      <dt>Tagger:</dt>
      <dd>{{ rt->tagger }}</dd>
      <dt>Date:</dt>
      <dd>
        {{ render datetime(tp, rt->tagger_time, TM_LONG)}}
      </dd>
      <dt>Message:</dt>
      <dd class="commit-msg">{{ rt->commit_msg }}</dd>
    </dl>
    <hr />
    <pre id="tag_commit">
      {{ rt->tag_commit }}
    </pre>
  </div>
</div>
{{ end }}

{{ define gotweb_render_diff(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	const struct querystring *qs = t->qs;
	FILE			*fp = t->fp;
	struct repo_commit	*rc = TAILQ_FIRST(&t->repo_commits);
	char			*line = NULL;
	size_t			 linesize = 0;
	ssize_t			 linelen;
	struct gotweb_url	 patch_url, tree_url = {
		.action = TREE,
		.index_page = -1,
		.path = qs->path[0] ? qs->path : NULL,
		.commit = rc->commit_id,
	};

	memcpy(&patch_url, &tree_url, sizeof(patch_url));
	patch_url.action = PATCH;
!}
<header class="subtitle">
  <h2>Commit Diff</h2>
</header>
<div id="diff_content">
  <div class="page_header_wrapper">
    <dl>
      <dt>Commit:</dt>
      <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
      <dt>From:</dt>
      <dd>{{ rc->author }}</dd>
      {{ if strcmp(rc->committer, rc->author) != 0 }}
        <dt>Via:</dt>
        <dd>{{ rc->committer }}</dd>
      {{ end }}
      <dt>Date:</dt>
      <dd>
        {{ render datetime(tp, rc->committer_time, TM_LONG) }}
      </dd>
      <dt>Message:</dt>
      <dd class="commit-msg">{{ rc->commit_msg }}</dd>
      <dt>Actions:</dt>
      <dd>
        <a href="{{ render gotweb_render_url(c, &patch_url) }}">
          Patch
        </a>
        {{" | "}}
        <a href="{{ render gotweb_render_url(c, &tree_url) }}">
          Tree
        </a>
      </dd>
    </dl>
  </div>
  <hr />
  <pre id="diff">
    {{ while (linelen = getline(&line, &linesize, fp)) != -1 }}
      {{ render diff_line(tp, line) }}
    {{ end }}
  </pre>
</div>
{{ finally }}
{! free(line); !}
{{ end }}

{{ define diff_line(struct template *tp, char *line )}}
{!
	const char		*color = NULL;
	char			*nl;

	if (!strncmp(line, "-", 1))
		color = "diff_minus";
	else if (!strncmp(line, "+", 1))
		color = "diff_plus";
	else if (!strncmp(line, "@@", 2))
		color = "diff_chunk_header";
	else if (!strncmp(line, "commit +", 8) ||
	    !strncmp(line, "commit -", 8) ||
	    !strncmp(line, "blob +", 6) ||
	    !strncmp(line, "blob -", 6) ||
	    !strncmp(line, "file +", 6) ||
	    !strncmp(line, "file -", 6))
		color = "diff_meta";
	else if (!strncmp(line, "from:", 5) || !strncmp(line, "via:", 4))
		color = "diff_author";
	else if (!strncmp(line, "date:", 5))
		color = "diff_date";

	nl = strchr(line, '\n');
	if (nl)
		*nl = '\0';
!}
<span class="diff_line {{ color }}">{{ line }}</span>{{"\n"}}
{{ end }}

{{ define gotweb_render_branches(struct template *tp,
    struct got_reflist_head *refs) }}
{!
	struct got_reflist_entry	*re;
!}
<header class='subtitle'>
  <h2>Branches</h2>
</header>
<div id="branches_content">
  {{ tailq-foreach re refs entry }}
    {{ if !got_ref_is_symbolic(re->ref) }}
      {{ render branch(tp, re) }}
    {{ end }}
  {{ end }}
</div>
{{ end }}

{{ define branch(struct template *tp, struct got_reflist_entry *re) }}
{!
	const struct got_error	*err;
	struct request		*c = tp->tp_arg;
	const struct querystring *qs = c->t->qs;
	const char		*refname;
	time_t			 age;
	struct gotweb_url	 url = {
		.action = SUMMARY,
		.index_page = -1,
		.path = qs->path[0] ? qs->path : NULL,
	};

	refname = got_ref_get_name(re->ref);

	err = got_get_repo_age(&age, c, refname);
	if (err) {
		log_warnx("%s: %s", __func__, err->msg);
		return -1;
	}

	if (strncmp(refname, "refs/heads/", 11) == 0)
		refname += 11;

	url.headref = refname;
!}
<section class="branches_wrapper">
  <div class="branches_age">
    {{ render datetime(tp, age, TM_DIFF) }}
  </div>
  <div class="branch">
    <a href="{{ render gotweb_render_url(c, &url) }}">{{ refname }}</a>
  </div>
  <div class="navs_wrapper">
    <div class="navs">
      <a href="{{ render gotweb_render_url(c, &url) }}">summary</a>
      {{" | "}}
      {! url.action = BRIEFS; !}
      <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
      {{" | "}}
      {! url.action = COMMITS; !}
      <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
    </div>
  </div>
  <hr />
</section>
{{ end }}

{{ define gotweb_render_summary(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct server		*srv = c->srv;
	struct transport	*t = c->t;
	struct got_reflist_head	*refs = &t->refs;
!}
<dl id="summary_wrapper" class="page_header_wrapper">
  {{ if srv->show_repo_description }}
    <dt>Description:</dt>
    <dd>{{ t->repo_dir->description }}</dd>
  {{ end }}
  {{ if srv->show_repo_owner }}
    <dt>Owner:</dt>
    <dd>{{ t->repo_dir->owner }}</dd>
  {{ end }}
  {{ if srv->show_repo_age }}
    <dt>Last Change:</dt>
    <dd>
      {{ render datetime(tp, t->repo_dir->age, TM_DIFF) }}
    </dd>
  {{ end }}
  {{ if srv->show_repo_cloneurl && t->repo_dir->url }}
    <dt>Clone URL:</dt>
    <dd><pre class="clone-url">{{ t->repo_dir->url }}</pre></dd>
  {{ if t->repo_dir->sshfp[GOTWEBD_SSHFP_ECDSA] }}
    <dt>ECDSA:</dt>
    <dd><pre class="clone-url">{{ t->repo_dir->sshfp[GOTWEBD_SSHFP_ECDSA] }}</pre></dd>
  {{ end }}
  {{ if t->repo_dir->sshfp[GOTWEBD_SSHFP_ED25519] }}
    <dt>ED25519:</dt>
    <dd><pre class="clone-url">{{ t->repo_dir->sshfp[GOTWEBD_SSHFP_ED25519] }}</pre></dd>
  {{ end }}
  {{ if t->repo_dir->sshfp[GOTWEBD_SSHFP_RSA] }}
    <dt>RSA:</dt>
    <dd><pre class="clone-url">{{ t->repo_dir->sshfp[GOTWEBD_SSHFP_RSA] }}</pre></dd>
  {{ end }}
  {{ end }}
</dl>
<div class="summary-briefs">
  {{ render gotweb_render_briefs(tp) }}
</div>
<div class="summary-branches">
  {{ render gotweb_render_branches(tp, refs) }}
</div>
<div class="summary-tags">
  {{ render gotweb_render_tags(tp) }}
</div>
<div class="summary-tree">
  <header class='subtitle'>
    <h2>Tree</h2>
  </header>
  <div id="tree_content">
    {{ render tree_listing(tp) }}
  </div>
</div>
{{ end }}

{{ define gotweb_render_blame(struct template *tp) }}
{!
	const struct got_error	*err;
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	const struct querystring *qs = t->qs;
	struct repo_commit	*rc = TAILQ_FIRST(&t->repo_commits);
	struct gotweb_url	 briefs_url, blob_url, raw_url;

	memset(&briefs_url, 0, sizeof(briefs_url));
	briefs_url.index_page = -1,
	briefs_url.action = BRIEFS,
	briefs_url.path = qs->path[0] ? qs->path : NULL;
	briefs_url.commit = qs->commit[0] ? qs->commit : NULL,
	briefs_url.folder = qs->folder[0] ? qs->folder : NULL,
	briefs_url.file = qs->file[0] ? qs->file : NULL,

	memcpy(&blob_url, &briefs_url, sizeof(blob_url));
	blob_url.action = BLOB;

	memcpy(&raw_url, &briefs_url, sizeof(raw_url));
	raw_url.action = BLOBRAW;
!}
<header class="subtitle">
  <h2>Blame</h2>
</header>
<div id="blame_content">
  <div class="page_header_wrapper">
    <dl>
      <dt>Date:</dt>
      <dd>
        {{ render datetime(tp, rc->committer_time, TM_LONG) }}
      </dd>
      <dt>Message:</dt>
      <dd class="commit-msg">{{ rc->commit_msg }}</dd>
      <dt>Actions:</dt>
      <dd>
        <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
          History
        </a>
        {{" | "}}
        <a href="{{ render gotweb_render_url(c, &blob_url) }}">
          Blob
        </a>
        {{" | "}}
        <a href="{{ render gotweb_render_url(c, &raw_url) }}">
          Raw File
        </a>
      </dd>
    </dl>
  </div>
  <hr />
  <pre id="blame">
    {!
	err = got_output_file_blame(c, &blame_line);
	if (err && err->code != GOT_ERR_CANCELLED)
		log_warnx("%s: got_output_file_blame: %s", __func__,
		    err->msg);
	if (err)
		return (-1);
    !}
  </pre>
</div>
{{ end }}

{{ define blame_line(struct template *tp, const char *line,
    struct blame_line *bline, int lprec, int lcur) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	struct repo_dir		*repo_dir = t->repo_dir;
	char			*committer, *s;
	struct gotweb_url	 url = {
		.action = DIFF,
		.index_page = -1,
		.path = repo_dir->name,
		.commit = bline->id_str,
	};

	s = strchr(bline->committer, '<');
	committer = s ? s + 1 : bline->committer;

	s = strchr(committer, '@');
	if (s)
		*s = '\0';
!}
<div class="blame_line">
  <span class="blame_number">{{ printf "%*d ", lprec, lcur }}</span>
  <span class="blame_hash">
    <a href="{{ render gotweb_render_url(c, &url) }}">
      {{ printf "%.8s", bline->id_str }}
    </a>
  </span>
  {{" "}}
  <span class="blame_date">{{ bline->datebuf }}</span>
  {{" "}}
  <span class="blame_author">{{ printf "%.9s", committer }}</span>
  {{" "}}
  <span class="blame_code">{{ line }}</span>
</div>
{{ end }}

{{ define gotweb_render_patch(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct transport	*t = c->t;
	struct repo_commit	*rc = TAILQ_FIRST(&t->repo_commits);
	struct tm		 tm;
	char			 buf[BUFSIZ], datebuf[64];
	size_t			 r;
	int			 w;

	if (gmtime_r(&rc->committer_time, &tm) == NULL ||
	    asctime_r(&tm, datebuf) == NULL)
		return (-1);

	datebuf[strcspn(datebuf, "\n")] = '\0';
!}
commit {{ rc->commit_id }} {{ "\n" }}
from: {{ rc->author | unsafe }} {{ "\n" }}
{{ if strcmp(rc->committer, rc->author) != 0 }}
via: {{ rc->committer | unsafe }} {{ "\n" }}
{{ end }}
date: {{ datebuf }} {{ " UTC" }} {{ "\n" }}
{{ "\n" }}
{{ rc->commit_msg | unsafe }} {{ "\n" }}
{!
	if (template_flush(tp) == -1)
		return (-1);
	for (;;) {
		r = fread(buf, 1, sizeof(buf), t->fp);
		if (r == 0)
			break;
		w = fcgi_write(c, buf, r);
		if (w == -1)
			break;
	}
!}
{{ 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,
		.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;
	struct tm		 tm;
	char			 rfc822[128];
	int			 r;
	char			*tag_name = rt->tag_name;
	struct gotweb_url	 tag = {
		.action = TAG,
		.index_page = -1,
		.path = repo_dir->name,
		.commit = rt->commit_id,
	};

	if (strncmp(tag_name, "refs/tags/", 10) == 0)
		tag_name += 10;

	if (gmtime_r(&rt->tagger_time, &tm) == NULL)
		return -1;
	r = strftime(rfc822, sizeof(rfc822), "%a, %d %b %Y %H:%M:%S GMT", &tm);
	if (r == 0)
		return 0;
!}
<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>
    {{ 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 }}

{{ define gotweb_render_unauthorized(struct template *tp) }}
<p>Wrong or missing authentication code</p>
{{ end }}

{{ define gotweb_render_refresh_redirect(struct template *tp) }}
{!
	struct request		*c = tp->tp_arg;
	struct server		*srv = c->srv;
	char			 prefix[MAX_DOCUMENT_URI * 2];
	const char		*css = srv->custom_css;
	const char		*url_root, *url_path;
	struct gotweb_url	url;

	strlcpy(prefix, "/", sizeof(prefix));

	url_root = srv->gotweb_url_root;
	while (url_root[0] == '/')
		url_root++;
	if (url_root[0] != '\0')
		strlcat(prefix, url_root, sizeof(prefix));

	url_path = srv->repos_url_path;
	while (url_path[0] == '/')
		url_path++;
	if (url_path[0] != '\0') {
		if (url_root[0] != '\0') {
			got_path_strip_trailing_slashes(prefix);
			strlcat(prefix, "/", sizeof(prefix));
		}
		strlcat(prefix, url_path, sizeof(prefix));
	}

	got_path_strip_trailing_slashes(prefix);

	memset(&url, 0, sizeof(url));
	url.action = INDEX;
!}
<html>
<head>
  <meta http-equiv="refresh" content="0;url={{ render gotweb_render_url(tp->tp_arg, &url) }}">
  <link rel="stylesheet" type="text/css" href="{{ prefix }}/{{ css }}" />
</head>
<body>
<p>
  Please <a href="{{ render gotweb_render_url(tp->tp_arg, &url) }}">click here</a>
  {{ " " }} if the page does not redirect automatically.
</body>
</html>
{{ end }}
