commit - d1f166363b86f21241be730505c1182508dc9d2c
commit + 2c251c14c84a96ddb3a968cd30fd889ba363f805
blob - 7aaea69b30edf716c5f17759fd96059ac9dd2286
blob + ed241a51a70f1885bc59f89cb7f41f4ce53b66bb
--- Makefile
+++ Makefile
.PHONY: release dist
.if make(regress) || make(obj) || make(clean) || make(release)
-SUBDIR += regress
+SUBDIR += regress gotweb
.endif
.include "got-version.mk"
diff -u got-dist.txt got-dist.txt.new
rm got-dist.txt.new
+web:
+ sed -i -e "s/MAKEWEB=No/MAKEWEB=Yes/" got-version.mk
+ ${MAKE} -C gotweb
+ sed -i -e "s/MAKEWEB=Yes/MAKEWEB=No/" got-version.mk
+
+web-install:
+ sed -i -e "s/MAKEWEB=No/MAKEWEB=Yes/" got-version.mk
+ ${MAKE} -C gotweb install
+ sed -i -e "s/MAKEWEB=Yes/MAKEWEB=No/" got-version.mk
+
.include <bsd.subdir.mk>
blob - bb6ed69a102b03fb61b3fafbe7e4cd7487693514
blob + 8f9b2cf6f568f007f0e1d1cce187828cc667f290
--- Makefile.inc
+++ Makefile.inc
.endif
.endif
+
+.if ${MAKEWEB} == "Yes"
+LDADD = -L${PREFIX}/lib -static -lkcgihtml -lkcgi -lz -lutil
+PREFIX = /usr/local
+CHROOT_DIR = /var/www
+GOTWEB_DIR = /cgi-bin/gotweb
+LIBEXECDIR = ${GOTWEB_DIR}/libexec
+LIBEXEC_DIR = ${CHROOT_DIR}${LIBEXECDIR}
+ETC_DIR = ${CHROOT_DIR}/etc
+EXPL_DIR = ${ETC_DIR}/examples
+HTTPD_DIR = ${CHROOT_DIR}/htdocs
+PROG_DIR = ${HTTPD_DIR}/${PROG}
+CGI_DIR = ${CHROOT_DIR}${GOTWEB_DIR}
+TMPL_DIR = ${CGI_DIR}/gw_tmpl
+.endif
blob - /dev/null
blob + 4ea3fdf6856195211f469ede4bc0b6c70d0ad0e1 (mode 644)
--- /dev/null
+++ gotweb/Makefile
+.PATH:${.CURDIR}/../lib
+
+SUBDIR = ../libexec
+
+.include "../got-version.mk"
+
+PROG = gotweb
+SRCS = gotweb.c parse.y blame.c commit_graph.c delta.c diff.c \
+ diffreg.c error.c fileindex.c object.c object_cache.c \
+ object_idset.c object_parse.c opentemp.c path.c pack.c \
+ privsep.c reference.c repository.c sha1.c worktree.c \
+ inflate.c buf.c rcsutil.c diff3.c lockfile.c \
+ deflate.c object_create.c delta_cache.c
+MAN = ${PROG}.8 ${PROG}.conf.5
+
+CPPFLAGS += -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${PREFIX}/include
+
+LDADD += -L${PREFIX}/lib -static -lkcgihtml -lkcgi -lz -lutil
+
+.if ${GOT_RELEASE} != "Yes"
+NOMAN = Yes
+.endif
+
+realinstall:
+ if [ ! -d ${CGI_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${CGI_DIR}; \
+ fi
+ ${INSTALL} -c -o www -g www -m 0755 ${PROG} ${CGI_DIR}/${PROG}
+ if [ ! -d ${TMPL_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${TMPL_DIR}; \
+ fi
+ ${INSTALL} -c -o www -g www -m 0755 files/cgi-bin/gw_tmpl/* ${TMPL_DIR}
+ if [ ! -d ${ETC_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${ETC_DIR}; \
+ fi
+ if [ ! -d ${EXPL_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${EXPL_DIR}; \
+ fi
+ ${INSTALL} -c -o www -g www -m 0755 files/etc/gotweb.conf \
+ ${ETC_DIR}/examples/gotweb.conf
+ if [ ! -d ${HTTPD_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${HTTPD_DIR}; \
+ fi
+ if [ ! -d ${PROG_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${PROG_DIR}; \
+ fi
+ ${INSTALL} -c -o www -g www -m 0755 files/htdocs/${PROG}/* ${PROG_DIR}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 46af06afd32c72f4d45ab5cb8e6d8124afee0876 (mode 644)
--- /dev/null
+++ gotweb/README
+***THIS IS NOT FINISHED CODE***
+gotweb README
blob - /dev/null
blob + c1ca944f5834b9a6f65edf7f890a8968edafdf78 (mode 644)
--- /dev/null
+++ gotweb/TODO
+Complete templates.
+Complete stylesheets.
+Complete gw_funcs
+----
+Remember items
+ description
+ cloneurl
blob - /dev/null
blob + 12819268eddbfa9b3a5d68d8897c937bb20f7cd4 (mode 644)
--- /dev/null
+++ gotweb/files/cgi-bin/gw_tmpl/index.tmpl
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>@@title@@</title>
+ @@head@@
+ </head>
+ <body>
+ <div id="gw_body">
+ <div id="header">
+ @@header@@
+ </div>
+ <div id="site_path">
+ @@sitepath@@
+ @@search@@
+ </div>
+ <div id="content">
+ @@content@@
+ </div>
+ @@siteowner@@
+ </div>
+ </body>
+</html>
blob - /dev/null
blob + 9b5a4fc668ccf2e333d79adfff2a4d9501126311 (mode 644)
--- /dev/null
+++ gotweb/files/cgi-bin/gw_tmpl/summary.tmpl
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>@@title@@</title>
+ @@head@@
+ </head>
+ <body>
+ <div id="gw_body">
+ <div id="header">
+ @@header@@
+ </div>
+ <div id="site_path">
+ @@sitepath@@
+ @@search@@
+ </div>
+ <div id="content">
+ <div id="summary_wrapper">
+ @@description@@
+ @@repo_owner@@
+ @@repo_age@@
+ @@cloneurl@@
+ </div>
+ @@summary_shortlog@@
+ @@summary_tags@@
+ @@summary_heads@@
+ </div>
+ @@siteowner@@
+ </div>
+ </body>
+</html>
blob - /dev/null
blob + c13c81b65d0c6ee672c348a1b1f122f822e97f10 (mode 644)
--- /dev/null
+++ gotweb/files/etc/gotweb.conf
+#
+# gotweb options
+# all paths relative to /var/www (httpd chroot jail)
+#
+
+got_repos_path "/got/public"
+got_www_path "/gotweb"
+
+#got_max_repos 100
+#got_max_repos_display 25
+got_max_commits_display 50
+
+got_site_name "my public repos"
+got_site_owner "Got Owner"
+got_site_link "repos"
+
+got_logo "got.png"
+got_logo_url "https://gameoftrees.org"
+
+# on by default
+#got_show_site_owner off
+#got_show_repo_owner off
+#got_show_repo_age false
+#got_show_repo_description no
+#got_show_repo_cloneurl off
blob - /dev/null
blob + f841f054bc2941b0cdca7e496ea69621671d6766 (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/android-chrome-192x192.png differ
blob - /dev/null
blob + 653a1510ce933f7fe9fbab2fcd171f04fa0b24cc (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/android-chrome-384x384.png differ
blob - /dev/null
blob + 460aa1299f8e9f37773618bcab2619794416fb49 (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/apple-touch-icon.png differ
blob - /dev/null
blob + b3930d0f047184047cb81d620436d91653438b8b (mode 644)
--- /dev/null
+++ gotweb/files/htdocs/gotweb/browserconfig.xml
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="/mstile-150x150.png"/>
+ <TileColor>#da532c</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
blob - /dev/null
blob + f6c1a7c289faa4a48e03c97e68b1ba7a11dfddd1 (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/favicon-16x16.png differ
blob - /dev/null
blob + 0ceea8c0eabe73e8d12cf106d73c34abb1999cb2 (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/favicon-32x32.png differ
blob - /dev/null
blob + ee414573031ea5b310539196d2530a1e52d49b64 (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/favicon.ico differ
blob - /dev/null
blob + 33933f80ee46217039804bc96672ede12b352b93 (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/got.png differ
blob - /dev/null
blob + 97ace786464b193baf1cd51e54016aea3016e62f (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/got_large.png differ
blob - /dev/null
blob + 133211bd0a02daf443d1c7c66ab17778f60f23e7 (mode 644)
--- /dev/null
+++ gotweb/files/htdocs/gotweb/gotweb.css
+/*
+ * Copyright (c) 2019 Jerome Kasper <neon.king.fr@gmail.com>
+ * Copyright (c) 2019 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.
+ */
+
+/* general sections */
+
+a {
+ color: #444444;
+ text-decoration: none;
+}
+a:hover {
+ color: Gold;
+ text-decoration: none;
+}
+body {
+ background-color: #ffffff;
+ color: #000000;
+ margin: 0;
+ padding: 0;
+ font-family: Arial, sans-serif;
+}
+#dotted_line {
+ clear: left;
+ float: left;
+ width: 100%;
+ border-top: 1px dotted #444444;
+}
+#header {
+ overflow: auto;
+ width: 100%;
+ background-image: linear-gradient(to right, White, LightSlateGray);
+}
+#header a {
+ color: #ffffff;
+ font-size: 1.2em;
+ text-decoration: none;
+}
+#header a:hover {
+ color: Gold;
+ font-size: 1.2em;
+ text-decoration: none;
+}
+#site_path {
+ clear: left;
+ float: left;
+ overflow: auto;
+ width: 100%;
+ background-color: #243647;
+}
+#site_link {
+ float: left;
+ width: 40%;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ color: #ffffff;
+ overflow: hidden;
+}
+#site_link a {
+ color: #ffffff;
+ text-decoration: none;
+}
+#search {
+ float: right;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#got_link {
+ float: left;
+ padding-bottom: 10px;
+ padding-top: 10px;
+}
+#content {
+ width: 100%;
+}
+#np_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ border-bottom: 1px dotted #444444;
+ background-color: #f5fcfb;
+ overflow: hidden;
+}
+#nav_prev {
+ float: left;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ overflow: visible;
+}
+#nav_next {
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ text-align: right;
+ overflow: hidden;
+}
+#navs_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: #ced7e0;
+}
+#np_navs {
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#site_owner_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#site_owner {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+
+/* index.tmpl */
+
+#index_header {
+ clear: left;
+ float: left;
+ overflow: auto;
+ width: 100%;
+ background-color: Khaki;
+}
+#index_header_project {
+ clear: left;
+ float: left;
+ width: 20%;
+ padding: 10px;
+}
+#index_header_description {
+ float: left;
+ width: 30%;
+ padding: 10px;
+}
+#index_header_owner {
+ float: left;
+ width: 12%;
+ padding: 10px;
+}
+#index_header_age {
+ padding: 10px;
+ overflow: hidden;
+}
+#index_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#index_project {
+ float: left;
+ width: 20%;
+ padding: 10px;
+ overflow: hidden;
+}
+#index_project_description {
+ float: left;
+ width: 30%;
+ padding: 10px;
+ overflow: auto;
+}
+#index_project_owner {
+ float: left;
+ width: 12%;
+ padding: 10px;
+ overflow: hidden;
+}
+#index_project_age {
+ float: left;
+ width: 14%;
+ padding: 10px;
+ overflow: visible;
+}
+#index_project a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_project a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+#index_project_navs a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_project_navs a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+#index_next a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_next a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+#index_prev a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_prev a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+
+/* summary.tmpl */
+
+#summary_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: Khaki;
+}
+#summary_description_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_description {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_repo_owner_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_repo_owner {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_last_change_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_last_change {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_cloneurl_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_cloneurl {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ overflow: auto;
+}
+#summary_shortlog_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#summary_shortlog_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_shortlog_content_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#summary_shortlog_content {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_tags_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#summary_tags_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_tags_content_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#summary_tags_content {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_heads_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#summary_heads_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#summary_heads_content_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#summary_heads_content {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
blob - /dev/null
blob + 791e49544c8c5f82a710137fee5a2a4becaad616 (mode 644)
--- /dev/null
+++ gotweb/files/htdocs/gotweb/index.html
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Refresh" content="0; url=/cgi-bin/gotweb/gotweb" />
+ </head>
+ <body>
+ <p><a href="/cgi-bin/gotweb/gotweb">gotweb</a></p>
+ </body>
+</html>
\ No newline at end of file
blob - /dev/null
blob + 0c47027971e9e0a5060e23fe73e7cb0399eacea8 (mode 644)
Binary files /dev/null and gotweb/files/htdocs/gotweb/mstile-150x150.png differ
blob - /dev/null
blob + 96e67c7c4b7cb9b1b395281fae8d7cffa834a991 (mode 644)
--- /dev/null
+++ gotweb/files/htdocs/gotweb/safari-pinned-tab.svg
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="400.000000pt" height="400.000000pt" viewBox="0 0 400.000000 400.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,400.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M0 1995 l0 -1215 2000 0 2000 0 0 1215 0 1215 -2000 0 -2000 0 0
+-1215z"/>
+</g>
+</svg>
blob - /dev/null
blob + a1553eb86b573da072c732c9aabac5a80968461f (mode 644)
--- /dev/null
+++ gotweb/files/htdocs/gotweb/site.webmanifest
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-384x384.png",
+ "sizes": "384x384",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
blob - /dev/null
blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644)
blob - /dev/null
blob + 3eac38fa7a0a786b60a6c550fe90389aa8e71e6f (mode 644)
--- /dev/null
+++ gotweb/gotweb.c
+/*
+ * Copyright (c) 2019 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2018, 2019 Stefan Sperling <stsp@openbsd.org>
+ * Copyright (c) 2014, 2015, 2017 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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 <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <got_object.h>
+#include <got_reference.h>
+#include <got_repository.h>
+#include <got_path.h>
+#include <got_cancel.h>
+#include <got_worktree.h>
+#include <got_diff.h>
+#include <got_commit_graph.h>
+#include <got_blame.h>
+#include <got_privsep.h>
+#include <got_opentemp.h>
+
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+#include "gotweb.h"
+#include "gotweb_ui.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+struct trans {
+ TAILQ_HEAD(dirs, gw_dir) gw_dirs;
+ struct gotweb_conf *gw_conf;
+ struct ktemplate *gw_tmpl;
+ struct khtmlreq *gw_html_req;
+ struct kreq *gw_req;
+ char *repo_name;
+ char *repo_path;
+ char *commit;
+ char *repo_file;
+ char *action_name;
+ unsigned int action;
+ unsigned int page;
+ unsigned int repos_total;
+ enum kmime mime;
+};
+
+enum gw_key {
+ KEY_PATH,
+ KEY_ACTION,
+ KEY_COMMIT_ID,
+ KEY_FILE,
+ KEY_PAGE,
+ KEY__MAX
+};
+
+struct gw_dir {
+ TAILQ_ENTRY(gw_dir) entry;
+ char *name;
+ char *owner;
+ char *description;
+ char *url;
+ char *age;
+ char *path;
+};
+
+enum tmpl {
+ TEMPL_HEAD,
+ TEMPL_HEADER,
+ TEMPL_SITEPATH,
+ TEMPL_SITEOWNER,
+ TEMPL_TITLE,
+ TEMPL_SEARCH,
+ TEMPL_DESCRIPTION,
+ TEMPL_CONTENT,
+ TEMPL_REPO_OWNER,
+ TEMPL_REPO_AGE,
+ TEMPL_CLONEURL,
+ TEMPL_SUMMARY_SHORTLOG,
+ TEMPL_SUMMARY_TAGS,
+ TEMPL_SUMMARY_HEADS,
+ TEMPL__MAX
+};
+
+static const char *const templs[TEMPL__MAX] = {
+ "head",
+ "header",
+ "sitepath",
+ "siteowner",
+ "title",
+ "search",
+ "description",
+ "content",
+ "repo_owner",
+ "repo_age",
+ "cloneurl",
+ "summary_shortlog",
+ "summary_tags",
+ "summary_heads",
+};
+
+static const struct kvalid gw_keys[KEY__MAX] = {
+ { kvalid_stringne, "path" },
+ { kvalid_stringne, "action" },
+ { kvalid_stringne, "commit" },
+ { kvalid_stringne, "file" },
+ { kvalid_int, "page" },
+};
+
+static struct gw_dir *gw_init_gw_dir(char *);
+
+static char *gw_get_repo_description(struct trans *,
+ char *);
+static char *gw_get_repo_owner(struct trans *,
+ char *);
+static char *gw_get_repo_age(struct trans *,
+ char *, char *);
+static char *gw_get_clone_url(struct trans *, char *);
+static char *gw_get_got_link(struct trans *);
+static char *gw_get_site_link(struct trans *);
+static char *gw_html_escape(const char *);
+
+static void gw_display_open(struct trans *, enum khttp,
+ enum kmime);
+static void gw_display_index(struct trans *,
+ const struct got_error *);
+
+static int gw_template(size_t, void *);
+
+static const struct got_error* apply_unveil(const char *, const char *);
+static const struct got_error* gw_load_got_paths(struct trans *);
+static const struct got_error* gw_load_got_path(struct trans *,
+ struct gw_dir *);
+static const struct got_error* gw_parse_querystring(struct trans *);
+
+static const struct got_error* gw_blame(struct trans *);
+static const struct got_error* gw_blob(struct trans *);
+static const struct got_error* gw_blob_diff(struct trans *);
+static const struct got_error* gw_commit(struct trans *);
+static const struct got_error* gw_commit_diff(struct trans *);
+static const struct got_error* gw_history(struct trans *);
+static const struct got_error* gw_index(struct trans *);
+static const struct got_error* gw_log(struct trans *);
+static const struct got_error* gw_raw(struct trans *);
+static const struct got_error* gw_shortlog(struct trans *);
+static const struct got_error* gw_snapshot(struct trans *);
+static const struct got_error* gw_tree(struct trans *);
+
+struct gw_query_action {
+ unsigned int func_id;
+ const char *func_name;
+ const struct got_error *(*func_main)(struct trans *);
+ char *template;
+};
+
+enum gw_query_actions {
+ GW_BLAME,
+ GW_BLOB,
+ GW_BLOBDIFF,
+ GW_COMMIT,
+ GW_COMMITDIFF,
+ GW_ERR,
+ GW_HISTORY,
+ GW_INDEX,
+ GW_LOG,
+ GW_RAW,
+ GW_SHORTLOG,
+ GW_SNAPSHOT,
+ GW_SUMMARY,
+ GW_TREE
+};
+
+static struct gw_query_action gw_query_funcs[] = {
+ { GW_BLAME, "blame", gw_blame, "gw_tmpl/index.tmpl" },
+ { GW_BLOB, "blob", gw_blob, "gw_tmpl/index.tmpl" },
+ { GW_BLOBDIFF, "blobdiff", gw_blob_diff, "gw_tmpl/index.tmpl" },
+ { GW_COMMIT, "commit", gw_commit, "gw_tmpl/index.tmpl" },
+ { GW_COMMITDIFF, "commit_diff", gw_commit_diff, "gw_tmpl/index.tmpl" },
+ { GW_ERR, NULL, NULL, "gw_tmpl/err.tmpl" },
+ { GW_HISTORY, "history", gw_history, "gw_tmpl/index.tmpl" },
+ { GW_INDEX, "index", gw_index, "gw_tmpl/index.tmpl" },
+ { GW_LOG, "log", gw_log, "gw_tmpl/index.tmpl" },
+ { GW_RAW, "raw", gw_raw, "gw_tmpl/index.tmpl" },
+ { GW_SHORTLOG, "shortlog", gw_shortlog, "gw_tmpl/index.tmpl" },
+ { GW_SNAPSHOT, "snapshot", gw_snapshot, "gw_tmpl/index.tmpl" },
+ { GW_SUMMARY, "summary", NULL, "gw_tmpl/summary.tmpl" },
+ { GW_TREE, "tree", gw_tree, "gw_tmpl/index.tmpl" },
+};
+
+static const struct got_error *
+apply_unveil(const char *repo_path, const char *repo_file)
+{
+ const struct got_error *err;
+
+ if (repo_path && repo_file) {
+ char *full_path;
+ if ((asprintf(&full_path, "%s/%s", repo_path, repo_file)) == -1)
+ return got_error_from_errno("asprintf unveil");
+ if (unveil(full_path, "r") != 0)
+ return got_error_from_errno2("unveil", full_path);
+ }
+
+ if (repo_path && unveil(repo_path, "r") != 0)
+ return got_error_from_errno2("unveil", repo_path);
+
+ if (unveil("/tmp", "rwc") != 0)
+ return got_error_from_errno2("unveil", "/tmp");
+
+ err = got_privsep_unveil_exec_helpers();
+ if (err != NULL)
+ return err;
+
+ if (unveil(NULL, NULL) != 0)
+ return got_error_from_errno("unveil");
+
+ return NULL;
+}
+
+static const struct got_error *
+gw_blame(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_blob(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_blob_diff(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_commit(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_commit_diff(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_history(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_index(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+ struct gw_dir *dir = NULL;
+ char *html, *navs, *next, *prev;
+ unsigned int prev_disp = 0, next_disp = 1, dir_c = 0;
+
+ error = gw_load_got_paths(gw_trans);
+ if (error && error->code != GOT_ERR_OK)
+ return error;
+
+ khttp_puts(gw_trans->gw_req, index_projects_header);
+
+ TAILQ_FOREACH(dir, &gw_trans->gw_dirs, entry)
+ dir_c++;
+
+ TAILQ_FOREACH(dir, &gw_trans->gw_dirs, entry) {
+ if (gw_trans->page > 0 && (gw_trans->page *
+ gw_trans->gw_conf->got_max_repos_display) > prev_disp) {
+ prev_disp++;
+ continue;
+ }
+
+ prev_disp++;
+ if((asprintf(&navs, index_navs, dir->name, dir->name, dir->name,
+ dir->name)) == -1)
+ return got_error_from_errno("asprintf");
+
+ if ((asprintf(&html, index_projects, dir->name, dir->name,
+ dir->description, dir->owner, dir->age, navs)) == -1)
+ return got_error_from_errno("asprintf");
+
+ khttp_puts(gw_trans->gw_req, html);
+
+ free(navs);
+ free(html);
+
+ if (gw_trans->gw_conf->got_max_repos_display == 0)
+ continue;
+
+ if (next_disp == gw_trans->gw_conf->got_max_repos_display)
+ khttp_puts(gw_trans->gw_req, np_wrapper_start);
+ else if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
+ (gw_trans->page > 0) &&
+ (next_disp == gw_trans->gw_conf->got_max_repos_display ||
+ prev_disp == gw_trans->repos_total))
+ khttp_puts(gw_trans->gw_req, np_wrapper_start);
+
+ if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
+ (gw_trans->page > 0) &&
+ (next_disp == gw_trans->gw_conf->got_max_repos_display ||
+ prev_disp == gw_trans->repos_total)) {
+ if ((asprintf(&prev, nav_prev,
+ gw_trans->page - 1)) == -1)
+ return got_error_from_errno("asprintf");
+ khttp_puts(gw_trans->gw_req, prev);
+ free(prev);
+ }
+
+ khttp_puts(gw_trans->gw_req, div_end);
+
+ if (gw_trans->gw_conf->got_max_repos_display > 0 &&
+ next_disp == gw_trans->gw_conf->got_max_repos_display &&
+ dir_c != (gw_trans->page + 1) *
+ gw_trans->gw_conf->got_max_repos_display) {
+ if ((asprintf(&next, nav_next,
+ gw_trans->page + 1)) == -1)
+ return got_error_from_errno("calloc");
+ khttp_puts(gw_trans->gw_req, next);
+ khttp_puts(gw_trans->gw_req, div_end);
+ free(next);
+ next_disp = 0;
+ break;
+ }
+
+ if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
+ (gw_trans->page > 0) &&
+ (next_disp == gw_trans->gw_conf->got_max_repos_display ||
+ prev_disp == gw_trans->repos_total))
+ khttp_puts(gw_trans->gw_req, div_end);
+
+ next_disp++;
+ }
+ return error;
+}
+
+static const struct got_error *
+gw_log(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_raw(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_shortlog(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_snapshot(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_tree(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gw_load_got_path(struct trans *gw_trans, struct gw_dir *gw_dir)
+{
+ const struct got_error *error = NULL;
+ DIR *dt;
+ char *dir_test;
+ bool opened = false;
+
+ if ((asprintf(&dir_test, "%s/%s/%s",
+ gw_trans->gw_conf->got_repos_path, gw_dir->name,
+ GOTWEB_GIT_DIR)) == -1)
+ return got_error_from_errno("asprintf");
+
+ dt = opendir(dir_test);
+ if (dt == NULL) {
+ free(dir_test);
+ } else {
+ gw_dir->path = strdup(dir_test);
+ opened = true;
+ goto done;
+ }
+
+ if ((asprintf(&dir_test, "%s/%s/%s",
+ gw_trans->gw_conf->got_repos_path, gw_dir->name,
+ GOTWEB_GOT_DIR)) == -1)
+ return got_error_from_errno("asprintf");
+
+ dt = opendir(dir_test);
+ if (dt == NULL)
+ free(dir_test);
+ else {
+ opened = true;
+ error = got_error(GOT_ERR_NOT_GIT_REPO);
+ goto errored;
+ }
+
+ if ((asprintf(&dir_test, "%s/%s",
+ gw_trans->gw_conf->got_repos_path, gw_dir->name)) == -1)
+ return got_error_from_errno("asprintf");
+
+ gw_dir->path = strdup(dir_test);
+
+done:
+ gw_dir->description = gw_get_repo_description(gw_trans,
+ gw_dir->path);
+ gw_dir->owner = gw_get_repo_owner(gw_trans, gw_dir->path);
+ gw_dir->age = gw_get_repo_age(gw_trans, gw_dir->path, NULL);
+ gw_dir->url = gw_get_clone_url(gw_trans, gw_dir->path);
+
+errored:
+ free(dir_test);
+ if (opened)
+ closedir(dt);
+ return error;
+}
+
+static const struct got_error *
+gw_load_got_paths(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+ DIR *d;
+ struct dirent **sd_dent;
+ struct gw_dir *gw_dir;
+ struct stat st;
+ unsigned int d_cnt, d_i;
+
+ if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1) {
+ error = got_error_from_errno("pledge");
+ return error;
+ }
+
+ error = apply_unveil(gw_trans->gw_conf->got_repos_path, NULL);
+ if (error)
+ return error;
+
+ d = opendir(gw_trans->gw_conf->got_repos_path);
+ if (d == NULL) {
+ error = got_error_from_errno2("opendir",
+ gw_trans->gw_conf->got_repos_path);
+ return error;
+ }
+
+ d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
+ alphasort);
+ if (d_cnt == -1) {
+ error = got_error_from_errno2("scandir",
+ gw_trans->gw_conf->got_repos_path);
+ return error;
+ }
+
+ for (d_i = 0; d_i < d_cnt; d_i++) {
+ if (gw_trans->gw_conf->got_max_repos > 0 &&
+ (d_i - 2) == gw_trans->gw_conf->got_max_repos)
+ break; /* account for parent and self */
+
+ if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
+ strcmp(sd_dent[d_i]->d_name, "..") == 0)
+ continue;
+
+ if ((gw_dir = gw_init_gw_dir(sd_dent[d_i]->d_name)) == NULL)
+ return got_error_from_errno("gw_dir malloc");
+
+ error = gw_load_got_path(gw_trans, gw_dir);
+ if (error && error->code == GOT_ERR_NOT_GIT_REPO)
+ continue;
+ else if (error)
+ return error;
+
+ if (lstat(gw_dir->path, &st) == 0 && S_ISDIR(st.st_mode) &&
+ !got_path_dir_is_empty(gw_dir->path)) {
+ TAILQ_INSERT_TAIL(&gw_trans->gw_dirs, gw_dir,
+ entry);
+ gw_trans->repos_total++;
+ }
+ }
+
+ closedir(d);
+ return error;
+}
+
+static const struct got_error *
+gw_parse_querystring(struct trans *gw_trans)
+{
+ const struct got_error *error = NULL;
+ struct kpair *p;
+ struct gw_query_action *action = NULL;
+ unsigned int i;
+
+ if (gw_trans->gw_req->fieldnmap[0]) {
+ error = got_error_from_errno("bad parse");
+ return error;
+ } else if ((p = gw_trans->gw_req->fieldmap[KEY_PATH])) {
+ /* define gw_trans->repo_path */
+ if ((asprintf(&gw_trans->repo_name, "%s", p->parsed.s)) == -1)
+ return got_error_from_errno("asprintf");
+
+ if ((asprintf(&gw_trans->repo_path, "%s/%s",
+ gw_trans->gw_conf->got_repos_path, p->parsed.s)) == -1)
+ return got_error_from_errno("asprintf");
+
+ if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
+ if ((asprintf(&gw_trans->commit, "%s",
+ p->parsed.s)) == -1)
+ return got_error_from_errno("asprintf");
+
+ /* get action and set function */
+ if ((p = gw_trans->gw_req->fieldmap[KEY_ACTION]))
+ for (i = 0; i < nitems(gw_query_funcs); i++) {
+ action = &gw_query_funcs[i];
+ if (action->func_name == NULL)
+ continue;
+
+ if (strcmp(action->func_name,
+ p->parsed.s) == 0) {
+ gw_trans->action = i;
+ if ((asprintf(&gw_trans->action_name,
+ "%s", action->func_name)) == -1)
+ return
+ got_error_from_errno(
+ "asprintf");
+
+ break;
+ }
+
+ action = NULL;
+ }
+
+ if ((p = gw_trans->gw_req->fieldmap[KEY_FILE]))
+ if ((asprintf(&gw_trans->repo_file, "%s",
+ p->parsed.s)) == -1)
+ return got_error_from_errno("asprintf");
+
+ if (action == NULL) {
+ error = got_error_from_errno("invalid action");
+ return error;
+ }
+ } else
+ gw_trans->action = GW_INDEX;
+
+ if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
+ gw_trans->page = p->parsed.i;
+
+ if (gw_trans->action == GW_RAW)
+ gw_trans->mime = KMIME_TEXT_PLAIN;
+
+ return error;
+}
+
+static struct gw_dir *
+gw_init_gw_dir(char *dir)
+{
+ struct gw_dir *gw_dir;
+
+ if ((gw_dir = malloc(sizeof(*gw_dir))) == NULL)
+ return NULL;
+
+ if ((asprintf(&gw_dir->name, "%s", dir)) == -1)
+ return NULL;
+
+ return gw_dir;
+}
+
+static void
+gw_display_open(struct trans *gw_trans, enum khttp code, enum kmime mime)
+{
+ khttp_head(gw_trans->gw_req, kresps[KRESP_ALLOW], "GET");
+ khttp_head(gw_trans->gw_req, kresps[KRESP_STATUS], "%s",
+ khttps[code]);
+ khttp_head(gw_trans->gw_req, kresps[KRESP_CONTENT_TYPE], "%s",
+ kmimetypes[mime]);
+ khttp_head(gw_trans->gw_req, "X-Content-Type-Options", "nosniff");
+ khttp_head(gw_trans->gw_req, "X-Frame-Options", "DENY");
+ khttp_head(gw_trans->gw_req, "X-XSS-Protection", "1; mode=block");
+ khttp_body(gw_trans->gw_req);
+}
+
+static void
+gw_display_index(struct trans *gw_trans, const struct got_error *err)
+{
+ gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
+ khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0);
+
+ if (err)
+ khttp_puts(gw_trans->gw_req, err->msg);
+ else
+ khttp_template(gw_trans->gw_req, gw_trans->gw_tmpl,
+ gw_query_funcs[gw_trans->action].template);
+
+ khtml_close(gw_trans->gw_html_req);
+}
+
+static int
+gw_template(size_t key, void *arg)
+{
+ const struct got_error *error = NULL;
+ struct trans *gw_trans = arg;
+ char *gw_got_link, *gw_site_link;
+ char *site_owner_name, *site_owner_name_h;
+ char *description, *description_h;
+ char *repo_owner, *repo_owner_h;
+ char *repo_age, *repo_age_h;
+ char *cloneurl, *cloneurl_h;
+
+ switch (key) {
+ case (TEMPL_HEAD):
+ khttp_puts(gw_trans->gw_req, head);
+ break;
+ case(TEMPL_HEADER):
+ gw_got_link = gw_get_got_link(gw_trans);
+ if (gw_got_link != NULL)
+ khttp_puts(gw_trans->gw_req, gw_got_link);
+
+ free(gw_got_link);
+ break;
+ case (TEMPL_SITEPATH):
+ gw_site_link = gw_get_site_link(gw_trans);
+ if (gw_site_link != NULL)
+ khttp_puts(gw_trans->gw_req, gw_site_link);
+
+ free(gw_site_link);
+ break;
+ case(TEMPL_TITLE):
+ if (gw_trans->gw_conf->got_site_name != NULL)
+ khtml_puts(gw_trans->gw_html_req,
+ gw_trans->gw_conf->got_site_name);
+
+ break;
+ case (TEMPL_SEARCH):
+ khttp_puts(gw_trans->gw_req, search);
+ break;
+ case(TEMPL_DESCRIPTION):
+ if (gw_trans->gw_conf->got_show_repo_description) {
+ description = gw_html_escape(
+ gw_get_repo_description(gw_trans,
+ gw_trans->repo_path));
+ if (description != NULL &&
+ (strcmp(description, "") != 0)) {
+ if ((asprintf(&description_h,
+ summary_description, description)) == -1)
+ return 0;
+
+ khttp_puts(gw_trans->gw_req, description_h);
+ free(description);
+ free(description_h);
+ }
+ }
+ break;
+ case(TEMPL_SITEOWNER):
+ if (gw_trans->gw_conf->got_site_owner != NULL &&
+ gw_trans->gw_conf->got_show_site_owner) {
+ site_owner_name =
+ gw_html_escape(gw_trans->gw_conf->got_site_owner);
+ if ((asprintf(&site_owner_name_h, site_owner,
+ site_owner_name))
+ == -1)
+ return 0;
+
+ khttp_puts(gw_trans->gw_req, site_owner_name_h);
+ free(site_owner_name);
+ free(site_owner_name_h);
+ }
+ break;
+ case(TEMPL_CONTENT):
+ error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
+ if (error)
+ khttp_puts(gw_trans->gw_req, error->msg);
+
+ break;
+ case(TEMPL_REPO_OWNER):
+ if (gw_trans->gw_conf->got_show_repo_owner) {
+ repo_owner = gw_html_escape(gw_get_repo_owner(gw_trans,
+ gw_trans->repo_path));
+ if ((asprintf(&repo_owner_h, summary_repo_owner,
+ repo_owner)) == -1)
+ return 0;
+
+ if (repo_owner != NULL &&
+ (strcmp(repo_owner, "") != 0)) {
+ khttp_puts(gw_trans->gw_req, repo_owner_h);
+ }
+
+ free(repo_owner_h);
+ }
+ break;
+ case(TEMPL_REPO_AGE):
+ if (gw_trans->gw_conf->got_show_repo_age) {
+ repo_age = gw_get_repo_age(gw_trans,
+ gw_trans->repo_path, NULL);
+ if (repo_age != NULL) {
+ if ((asprintf(&repo_age_h, summary_last_change,
+ repo_age)) == -1)
+ return 0;
+ khttp_puts(gw_trans->gw_req, repo_age_h);
+ free(repo_age);
+ free(repo_age_h);
+ }
+ }
+ break;
+ case(TEMPL_CLONEURL):
+ if (gw_trans->gw_conf->got_show_repo_cloneurl) {
+ cloneurl = gw_html_escape(gw_get_clone_url(gw_trans,
+ gw_trans->repo_path));
+ if (cloneurl != NULL) {
+ if ((asprintf(&cloneurl_h,
+ summary_cloneurl, cloneurl)) == -1)
+ return 0;
+
+ khttp_puts(gw_trans->gw_req, cloneurl_h);
+ free(cloneurl);
+ free(cloneurl_h);
+ }
+
+ }
+ break;
+ case(TEMPL_SUMMARY_SHORTLOG):
+ khttp_puts(gw_trans->gw_req, summary_shortlog);
+ break;
+ case(TEMPL_SUMMARY_TAGS):
+ khttp_puts(gw_trans->gw_req, summary_tags);
+ break;
+ case(TEMPL_SUMMARY_HEADS):
+ khttp_puts(gw_trans->gw_req, summary_heads);
+ break;
+ default:
+ return 0;
+ break;
+ }
+ return 1;
+}
+
+static char *
+gw_get_repo_description(struct trans *gw_trans, char *dir)
+{
+ FILE *f;
+ char *description = NULL, *d_file = NULL;
+ unsigned int len;
+
+ if (gw_trans->gw_conf->got_show_repo_description == false)
+ goto err;
+
+ if ((asprintf(&d_file, "%s/description", dir)) == -1)
+ goto err;
+
+ if ((f = fopen(d_file, "r")) == NULL)
+ goto err;
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f) + 1;
+ fseek(f, 0, SEEK_SET);
+ if ((description = calloc(len, sizeof(char *))) == NULL)
+ goto err;
+
+ fread(description, 1, len, f);
+ fclose(f);
+ free(d_file);
+ return description;
+err:
+ if ((asprintf(&description, "%s", "")) == -1)
+ return NULL;
+
+ return description;
+}
+
+static char *
+gw_get_repo_age(struct trans *gw_trans, char *dir, char *repo_ref)
+{
+ const struct got_error *error = NULL;
+ struct got_object_id *id = NULL;
+ struct got_repository *repo = NULL;
+ struct got_commit_object *commit = NULL;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
+ struct got_reference *head_ref;
+ time_t committer_time = 0, cmp_time = 0, diff_time;
+ char *repo_age = NULL, *years = "years ago", *months = "months ago";
+ char *weeks = "weeks ago", *days = "days ago", *hours = "hours ago";
+ char *minutes = "minutes ago", *seconds = "seconds ago";
+ char *now = "right now";
+
+ SIMPLEQ_INIT(&refs);
+ if (gw_trans->gw_conf->got_show_repo_age == false) {
+ asprintf(&repo_age, "");
+ return repo_age;
+ }
+ error = got_repo_open(&repo, dir, NULL);
+ if (error != NULL)
+ goto err;
+
+ error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
+ NULL);
+ if (error != NULL)
+ goto err;
+
+ const char *refname;
+ SIMPLEQ_FOREACH(re, &refs, entry) {
+ refname = got_ref_get_name(re->ref);
+ error = got_ref_open(&head_ref, repo, refname, 0);
+ if (error != NULL)
+ goto err;
+
+ error = got_ref_resolve(&id, repo, head_ref);
+ got_ref_close(head_ref);
+ if (error != NULL)
+ goto err;
+
+ error = got_object_open_as_commit(&commit, repo, id);
+ if (error != NULL)
+ goto err;
+
+ committer_time =
+ got_object_commit_get_committer_time(commit);
+ if (repo_ref != NULL && (strcmp(refname, repo_ref) == 0)) {
+ cmp_time = 0;
+ break;
+ }
+
+ if (committer_time > cmp_time)
+ cmp_time = committer_time;
+ }
+
+ if (repo_ref != NULL && (strcmp(refname, repo_ref) != 0)) {
+ asprintf(&repo_age, "");
+ goto noref;
+ }
+
+ if (cmp_time != 0)
+ committer_time = cmp_time;
+
+ diff_time = time(NULL) - committer_time;
+ if (diff_time > 60 * 60 * 24 * 365 * 2)
+ asprintf(&repo_age, "%lld %s", (diff_time / 60 / 60 / 24 / 365),
+ years);
+ else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2)
+ asprintf(&repo_age, "%lld %s", (diff_time / 60 / 60 / 24 /
+ (365 / 12)), months);
+ else if (diff_time > 60 * 60 * 24 * 7 * 2)
+ asprintf(&repo_age, "%lld %s", (diff_time / 60 / 60 / 24 / 7),
+ weeks);
+ else if (diff_time > 60 * 60 * 24 * 2)
+ asprintf(&repo_age, "%lld %s", (diff_time / 60 / 60 / 24),
+ days);
+ else if (diff_time > 60 * 60 * 2)
+ asprintf(&repo_age, "%lld %s", (diff_time / 60 / 60), hours);
+ else if (diff_time > 60 * 2)
+ asprintf(&repo_age, "%lld %s", (diff_time / 60), minutes);
+ else if (diff_time > 2)
+ asprintf(&repo_age, "%lld %s", diff_time, seconds);
+ else
+ asprintf(&repo_age, "%s", now);
+
+noref:
+ got_ref_list_free(&refs);
+ free(id);
+ return repo_age;
+err:
+ if ((asprintf(&repo_age, "%s", error->msg)) == -1)
+ return NULL;
+
+ return repo_age;
+}
+
+static char *
+gw_get_repo_owner(struct trans *gw_trans, char *dir)
+{
+ FILE *f;
+ char *owner = NULL, *d_file = NULL;
+ char *gotweb = "[gotweb]", *gitweb = "[gitweb]", *gw_owner = "owner";
+ char *comp, *pos, *buf;
+ unsigned int i;
+
+ if (gw_trans->gw_conf->got_show_repo_owner == false)
+ goto err;
+
+ if ((asprintf(&d_file, "%s/config", dir)) == -1)
+ goto err;
+
+ if ((f = fopen(d_file, "r")) == NULL)
+ goto err;
+
+ if ((buf = calloc(BUFFER_SIZE, sizeof(char *))) == NULL)
+ goto err;
+
+ while ((fgets(buf, BUFFER_SIZE, f)) != NULL) {
+ if ((pos = strstr(buf, gotweb)) != NULL)
+ break;
+
+ if ((pos = strstr(buf, gitweb)) != NULL)
+ break;
+ }
+
+ if (pos == NULL)
+ goto err;
+
+ do {
+ fgets(buf, BUFFER_SIZE, f);
+ } while ((comp = strcasestr(buf, gw_owner)) == NULL);
+
+ if (comp == NULL)
+ goto err;
+
+ if (strncmp(gw_owner, comp, strlen(gw_owner)) != 0)
+ goto err;
+
+ for (i = 0; i < 2; i++) {
+ owner = strsep(&buf, "\"");
+ }
+
+ if (owner == NULL)
+ goto err;
+
+ fclose(f);
+ free(d_file);
+ return owner;
+err:
+ if ((asprintf(&owner, "%s", "")) == -1)
+ return NULL;
+
+ return owner;
+}
+
+static char *
+gw_get_clone_url(struct trans *gw_trans, char *dir)
+{
+ FILE *f;
+ char *url = NULL, *d_file = NULL;
+ unsigned int len;
+
+ if ((asprintf(&d_file, "%s/cloneurl", dir)) == -1)
+ return NULL;
+
+ if ((f = fopen(d_file, "r")) == NULL)
+ return NULL;
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f) + 1;
+ fseek(f, 0, SEEK_SET);
+
+ if ((url = calloc(len, sizeof(char *))) == NULL)
+ return NULL;
+
+ fread(url, 1, len, f);
+ fclose(f);
+ free(d_file);
+ return url;
+}
+
+static char *
+gw_get_got_link(struct trans *gw_trans)
+{
+ char *link;
+
+ if ((asprintf(&link, got_link, gw_trans->gw_conf->got_logo_url,
+ gw_trans->gw_conf->got_logo)) == -1)
+ return NULL;
+
+ return link;
+}
+
+static char *
+gw_get_site_link(struct trans *gw_trans)
+{
+ char *link, *repo = "", *action = "";
+
+ if (gw_trans->repo_name != NULL)
+ if ((asprintf(&repo, " / <a href='?path=%s&action=summary'>%s" \
+ "</a>", gw_trans->repo_name, gw_trans->repo_name)) == -1)
+ return NULL;
+
+ if (gw_trans->action_name != NULL)
+ if ((asprintf(&action, " / %s", gw_trans->action_name)) == -1)
+ return NULL;
+
+ if ((asprintf(&link, site_link, GOTWEB,
+ gw_trans->gw_conf->got_site_link, repo, action)) == -1)
+ return NULL;
+
+ return link;
+}
+
+static char *
+gw_html_escape(const char *html)
+{
+ char *escaped_str = NULL, *buf;
+ char c[1];
+ size_t sz, i;
+
+ if ((buf = calloc(BUFFER_SIZE, sizeof(char *))) == NULL)
+ return NULL;
+
+ if (html == NULL)
+ return NULL;
+ else
+ if ((sz = strlen(html)) == 0)
+ return NULL;
+
+ /* only work with BUFFER_SIZE */
+ if (BUFFER_SIZE < sz)
+ sz = BUFFER_SIZE;
+
+ for (i = 0; i < sz; i++) {
+ c[0] = html[i];
+ switch (c[0]) {
+ case ('>'):
+ strcat(buf, ">");
+ break;
+ case ('&'):
+ strcat(buf, "&");
+ break;
+ case ('<'):
+ strcat(buf, "<");
+ break;
+ case ('"'):
+ strcat(buf, """);
+ break;
+ case ('\''):
+ strcat(buf, "'");
+ break;
+ case ('\n'):
+ strcat(buf, "<br />");
+ default:
+ strcat(buf, &c[0]);
+ break;
+ }
+ }
+ asprintf(&escaped_str, "%s", buf);
+ free(buf);
+ return escaped_str;
+}
+
+int
+main()
+{
+ const struct got_error *error = NULL;
+ struct trans *gw_trans;
+ struct gw_dir *dir = NULL, *tdir;
+ const char *page = "index";
+ bool gw_malloc = true;
+
+ if ((gw_trans = malloc(sizeof(struct trans))) == NULL)
+ errx(1, "malloc");
+
+ if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
+ errx(1, "malloc");
+
+ if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
+ errx(1, "malloc");
+
+ if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
+ errx(1, "malloc");
+
+ if (KCGI_OK != khttp_parse(gw_trans->gw_req, gw_keys, KEY__MAX,
+ &page, 1, 0))
+ errx(1, "khttp_parse");
+
+ if ((gw_trans->gw_conf =
+ malloc(sizeof(struct gotweb_conf))) == NULL) {
+ gw_malloc = false;
+ error = got_error_from_errno("malloc");
+ goto err;
+ }
+
+ TAILQ_INIT(&gw_trans->gw_dirs);
+
+ gw_trans->page = 0;
+ gw_trans->repos_total = 0;
+ gw_trans->repo_path = NULL;
+ gw_trans->commit = NULL;
+ gw_trans->mime = KMIME_TEXT_HTML;
+ gw_trans->gw_tmpl->key = templs;
+ gw_trans->gw_tmpl->keysz = TEMPL__MAX;
+ gw_trans->gw_tmpl->arg = gw_trans;
+ gw_trans->gw_tmpl->cb = gw_template;
+ error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
+
+err:
+ if (error) {
+ gw_trans->mime = KMIME_TEXT_PLAIN;
+ gw_trans->action = GW_ERR;
+ gw_display_index(gw_trans, error);
+ goto done;
+ }
+
+ error = gw_parse_querystring(gw_trans);
+ if (error)
+ goto err;
+
+ gw_display_index(gw_trans, error);
+
+done:
+ if (gw_malloc) {
+ free(gw_trans->gw_conf->got_repos_path);
+ free(gw_trans->gw_conf->got_www_path);
+ free(gw_trans->gw_conf->got_site_name);
+ free(gw_trans->gw_conf->got_site_owner);
+ free(gw_trans->gw_conf->got_site_link);
+ free(gw_trans->gw_conf->got_logo);
+ free(gw_trans->gw_conf->got_logo_url);
+ free(gw_trans->gw_conf);
+ free(gw_trans->commit);
+ free(gw_trans->repo_path);
+ free(gw_trans->repo_name);
+ free(gw_trans->repo_file);
+ free(gw_trans->action_name);
+
+ TAILQ_FOREACH_SAFE(dir, &gw_trans->gw_dirs, entry, tdir) {
+ free(dir->name);
+ free(dir->description);
+ free(dir->age);
+ free(dir->url);
+ free(dir->path);
+ free(dir);
+ }
+
+ }
+
+ khttp_free(gw_trans->gw_req);
+ return EXIT_SUCCESS;
+}
blob - /dev/null
blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644)
blob - /dev/null
blob + 3ed34580241af652fe0f01829f678efa82cfd317 (mode 644)
--- /dev/null
+++ gotweb/gotweb.h
+/*
+ * Copyright (c) 2019 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2018, 2019 Stefan Sperling <stsp@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef GOTWEB_H
+#define GOTWEB_H
+
+#include <stdbool.h>
+
+#include <got_error.h>
+
+#define GOTWEB_CONF "/etc/gotweb.conf"
+#define GOTWEB_TMPL_DIR "/cgi-bin/gw_tmpl"
+#define GOTWEB "/cgi-bin/gotweb/gotweb"
+
+#define GOTWEB_GOT_DIR ".got"
+#define GOTWEB_GIT_DIR ".git"
+
+#define D_GOTPATH "/got/public"
+#define D_GOTWWW "/gotweb"
+#define D_SITENAME "Gotweb"
+#define D_SITEOWNER "Got Owner"
+#define D_SITELINK "Repos"
+#define D_GOTLOGO "got.png"
+#define D_GOTURL "https://gameoftrees.org"
+
+#define D_SHOWROWNER true
+#define D_SHOWSOWNER true
+#define D_SHOWAGE true
+#define D_SHOWDESC true
+#define D_SHOWURL true
+#define D_MAXREPO 0
+#define D_MAXREPODISP 25
+#define D_MAXCOMMITDISP 25
+
+#define BUFFER_SIZE 2048
+
+struct gotweb_conf {
+ char *got_repos_path;
+ char *got_www_path;
+ char *got_site_name;
+ char *got_site_owner;
+ char *got_site_link;
+ char *got_logo;
+ char *got_logo_url;
+
+ size_t got_max_repos;
+ size_t got_max_repos_display;
+ size_t got_max_commits_display;
+
+ bool got_show_site_owner;
+ bool got_show_repo_owner;
+ bool got_show_repo_age;
+ bool got_show_repo_description;
+ bool got_show_repo_cloneurl;
+};
+
+const struct got_error* parse_conf(const char *, struct gotweb_conf *);
+
+#endif /* GOTWEB_H */
blob - /dev/null
blob + a058b737cb68fb43826c8186cc91f652fbd6e046 (mode 644)
--- /dev/null
+++ gotweb/gotweb_ui.h
+/*
+ * Copyright (c) 2019 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.
+ */
+
+/*
+ * header nav
+ *
+ * ***index
+ * search
+ * Projects
+ * Project|Description|Owner|Last Commit
+ * DIV (summary|shortlog|log|tree)
+ * ***summary
+ * repo navs | search
+ * repo description
+ * description
+ * owner
+ * last commit
+ * URL
+ * shortlog
+ * Date|committer|commit description (commit|commitdiff|tree|snapshot)
+ * heads
+ * create date | head (shortlog|log|tree)
+ *
+ *
+ *
+ * footer
+ */
+
+#ifndef GOTWEB_UI_H
+#define GOTWEB_UI_H
+
+/* general html */
+
+char *head =
+ "<meta name='viewport' content='initial-scale=1.0," \
+ " user-scalable=no' />" \
+ "<meta charset='utf-8' />" \
+ "<meta name='msapplication-TileColor' content='#da532c' />" \
+ "<meta name='theme-color' content='#ffffff' />" \
+ "<link rel='apple-touch-icon' sizes='180x180'" \
+ " href='/apple-touch-icon.png' />" \
+ "<link rel='icon' type='image/png' sizes='32x32'" \
+ " href='/favicon-32x32.png' />" \
+ "<link rel='icon' type='image/png' sizes='16x16'" \
+ " href='/favicon-16x16.png' />" \
+ "<link rel='manifest' href='/site.webmanifest' />" \
+ "<link rel='mask-icon' href='/safari-pinned-tab.svg'" \
+ " color='#5bbad5' />" \
+ "<link rel='stylesheet' type='text/css' href='/gotweb.css' />";
+
+char *got_link =
+ "<div id='got_link'>" \
+ "<a href='%s' target='_sotd'><img src='/%s' alt='logo' /></a>" \
+ "</div>";
+
+char *site_link =
+ "<div id='site_link'>" \
+ "<a href='%s'>%s</a> %s %s" \
+ "</div>";
+
+char *site_owner =
+ "<div id='site_owner_wrapper'><div id='site_owner'>%s</div></div>";
+
+char *search =
+ "<div id='search'>" \
+ "<form method='POST'>" \
+ "<input type='search' id='got-search' name='got-search' size='15'" \
+ " maxlength='50' />" \
+ "<button>Search</button>" \
+ "</form>" \
+ "</div>";
+
+char *np_wrapper_start =
+ "<div id='np_wrapper'>" \
+ "<div id='nav_prev'>";
+
+char *div_end =
+ "</div>";
+
+char *nav_next =
+ "<div id='nav_next'>" \
+ "<a href='?page=%d'>Next<a/>" \
+ "</div>";
+
+char *nav_prev =
+ "<a href='?page=%d'>Previous<a/>";
+
+/* index.tmpl */
+
+char *index_projects_header =
+ "<div id='index_header'>" \
+ "<div id='index_header_project'>Project</div>" \
+ "<div id='index_header_description'>Description</div>" \
+ "<div id='index_header_owner'>Owner</div>" \
+ "<div id='index_header_age'>Last Change</div>" \
+ "</div>";
+
+char *index_projects =
+ "<div id='index_wrapper'>" \
+ "<div id='index_project'>" \
+ "<a href='?path=%s&action=summary'>%s</a>" \
+ "</div>" \
+ "<div id='index_project_description'>%s</div>" \
+ "<div id='index_project_owner'>%s</div>" \
+ "<div id='index_project_age'>%s</div>" \
+ "<div id='navs_wrapper'>" \
+ "<div id='np_navs'>%s</div>" \
+ "</div>" \
+ "</div>" \
+ "<div id='dotted_line'></div>";
+
+char *index_navs =
+ "<a href='?path=%s&action=summary'>summary</a> | " \
+ "<a href='?path=%s&action=shortlog'>shortlog</a> | " \
+ "<a href='?path=%s&action=log'>log</a> | " \
+ "<a href='?path=%s&action=tree'>tree</a>";
+
+/* summary.tmpl */
+
+char *summary_description =
+ "<div id='summary_description_title'>Description: </div>" \
+ "<div id='summary_description'>%s</div>";
+
+char *summary_repo_owner =
+ "<div id='summary_repo_owner_title'>Owner: </div>" \
+ "<div id='summary_repo_owner'>%s</div>";
+
+char *summary_last_change =
+ "<div id='summary_last_change_title'>Last Change: </div>" \
+ "<div id='summary_last_change'>%s</div>";
+
+char *summary_cloneurl =
+ "<div id='summary_cloneurl_title'>Clone URL: </div>" \
+ "<div id='summary_cloneurl'>%s</div>";
+
+char *summary_shortlog =
+ "<div id='summary_shortlog_title_wrapper'>" \
+ "<div id='summary_shortlog_title'>Shortlog</div></div>" \
+ "<div id='summary_shortlog_content_wrapper'>" \
+ "<div id='summary_shortlog_content'>%s</div>" \
+ "</div>";
+
+char *summary_tags =
+ "<div id='summary_tags_title_wrapper'>" \
+ "<div id='summary_tags_title'>Tags</div></div>" \
+ "<div id='summary_tags_content_wrapper'>" \
+ "<div id='summary_tags_content'>%s</div>" \
+ "</div>";
+
+char *summary_heads =
+ "<div id='summary_heads_title_wrapper'>" \
+ "<div id='summary_heads_title'>Heads</div></div>" \
+ "<div id='summary_heads_content_wrapper'>" \
+ "<div id='summary_heads_content'>%s</div>" \
+ "</div>";
+
+#endif /* GOTWEB_UI_H */
blob - /dev/null
blob + f066d2df1f07bdd931a62cbdb7fb00a3b9bd4989 (mode 644)
--- /dev/null
+++ gotweb/parse.y
+/*
+ * Copyright (c) 2019 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gotweb.h"
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+ const struct got_error* error;
+} *file, *topfile;
+struct file *pushfile(const char *);
+int popfile(void);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)))
+ __attribute__((__nonnull__ (1)));
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+
+static const struct got_error* gerror = NULL;
+char *syn_err;
+
+struct gotweb_conf *gw_conf;
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token GOT_WWW_PATH GOT_MAX_REPOS GOT_SITE_NAME GOT_SITE_OWNER GOT_SITE_LINK
+%token GOT_LOGO GOT_LOGO_URL GOT_SHOW_REPO_OWNER GOT_SHOW_REPO_AGE
+%token GOT_SHOW_REPO_DESCRIPTION GOT_MAX_REPOS_DISPLAY GOT_REPOS_PATH
+%token GOT_MAX_COMMITS_DISPLAY ON ERROR GOT_SHOW_SITE_OWNER
+%token GOT_SHOW_REPO_CLONEURL
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.number> boolean
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar main '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+boolean : STRING {
+ if (strcasecmp($1, "true") == 0 ||
+ strcasecmp($1, "yes") == 0)
+ $$ = 1;
+ else if (strcasecmp($1, "false") == 0 ||
+ strcasecmp($1, "off") == 0 ||
+ strcasecmp($1, "no") == 0)
+ $$ = 0;
+ else {
+ yyerror("invalid boolean value '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | ON { $$ = 1; }
+ ;
+
+main : GOT_REPOS_PATH STRING {
+ if ((gw_conf->got_repos_path = strdup($2)) == NULL)
+ errx(1, "out of memory");
+ }
+ | GOT_WWW_PATH STRING {
+ if ((gw_conf->got_www_path = strdup($2)) == NULL)
+ errx(1, "out of memory");
+ }
+ | GOT_MAX_REPOS NUMBER {
+ if ($2 > 0)
+ gw_conf->got_max_repos = $2;
+ }
+ | GOT_SITE_NAME STRING {
+ if ((gw_conf->got_site_name = strdup($2)) == NULL)
+ errx(1, "out of memory");
+ }
+ | GOT_SITE_OWNER STRING {
+ if ((gw_conf->got_site_owner = strdup($2)) == NULL)
+ errx(1, "out of memory");
+ }
+ | GOT_SITE_LINK STRING {
+ if ((gw_conf->got_site_link = strdup($2)) == NULL)
+ errx(1, "out of memory");
+ }
+ | GOT_LOGO STRING {
+ if ((gw_conf->got_logo = strdup($2)) == NULL)
+ errx(1, "out of memory");
+ }
+ | GOT_LOGO_URL STRING {
+ if ((gw_conf->got_logo_url = strdup($2)) == NULL)
+ errx(1, "out of memory");
+ }
+ | GOT_SHOW_SITE_OWNER boolean {
+ gw_conf->got_show_site_owner = $2;
+ }
+ | GOT_SHOW_REPO_OWNER boolean {
+ gw_conf->got_show_repo_owner = $2;
+ }
+ | GOT_SHOW_REPO_AGE boolean { gw_conf->got_show_repo_age = $2; }
+ | GOT_SHOW_REPO_DESCRIPTION boolean {
+ gw_conf->got_show_repo_description = $2;
+ }
+ | GOT_SHOW_REPO_CLONEURL boolean {
+ gw_conf->got_show_repo_cloneurl = $2;
+ }
+ | GOT_MAX_REPOS_DISPLAY NUMBER {
+ if ($2 > 0)
+ gw_conf->got_max_repos_display = $2;
+ }
+ | GOT_MAX_COMMITS_DISPLAY NUMBER {
+ if ($2 > 0)
+ gw_conf->got_max_commits_display = $2;
+ }
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg = NULL;
+ static char err_msg[512];
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (vasprintf(&msg, fmt, ap) == -1)
+ errx(1, "yyerror vasprintf");
+ va_end(ap);
+ snprintf(err_msg, sizeof(err_msg), "%s:%d: %s", file->name,
+ yylval.lineno, msg);
+ gerror = got_error_from_errno2("parse_error", err_msg);
+
+ free(msg);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "got_logo", GOT_LOGO },
+ { "got_logo_url", GOT_LOGO_URL },
+ { "got_max_commits_display", GOT_MAX_COMMITS_DISPLAY },
+ { "got_max_repos", GOT_MAX_REPOS },
+ { "got_max_repos_display", GOT_MAX_REPOS_DISPLAY },
+ { "got_repos_path", GOT_REPOS_PATH },
+ { "got_show_repo_age", GOT_SHOW_REPO_AGE },
+ { "got_show_repo_cloneurl", GOT_SHOW_REPO_CLONEURL },
+ { "got_show_repo_description", GOT_SHOW_REPO_DESCRIPTION },
+ { "got_show_repo_owner", GOT_SHOW_REPO_OWNER },
+ { "got_show_site_owner", GOT_SHOW_SITE_OWNER },
+ { "got_site_link", GOT_SITE_LINK },
+ { "got_site_name", GOT_SITE_NAME },
+ { "got_site_owner", GOT_SITE_OWNER },
+ { "got_www_path", GOT_WWW_PATH },
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+u_char *parsebuf;
+int parseindex;
+u_char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ if ((c = getc(file->stream)) == EOF) {
+ yyerror("reached end of file while parsing "
+ "quoted string");
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = getc(file->stream)) == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ while (c == EOF) {
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ c = getc(file->stream);
+ }
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ if (pushback_index)
+ c = pushback_buffer[--pushback_index];
+ else
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ u_char buf[8096];
+ u_char *p;
+ int quotec, next, c;
+ int token;
+
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || next == ' ' ||
+ next == '\t')
+ c = next;
+ else if (next == '\n') {
+ file->lineno++;
+ continue;
+ } else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ } else if (c == '\0') {
+ yyerror("syntax error");
+ return (findeol());
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ errx(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((size_t)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '/' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_' || c == '*') {
+ do {
+ *p++ = c;
+ if ((size_t)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ errx(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+struct file *
+pushfile(const char *name)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+ gerror = got_error(GOT_ERR_NO_SPACE);
+ return (NULL);
+ }
+ if ((nfile->name = strdup(name)) == NULL) {
+ gerror = got_error(GOT_ERR_NO_SPACE);
+ free(nfile);
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ gerror = got_error_from_errno2("parse_conf", nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = 1;
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+ prev->errors += file->errors;
+
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file);
+ file = prev;
+ return (file ? 0 : EOF);
+}
+
+const struct got_error*
+parse_conf(const char *filename, struct gotweb_conf *gconf)
+{
+ static const struct got_error* error = NULL;
+
+ gw_conf = gconf;
+ if ((gw_conf->got_repos_path = strdup(D_GOTPATH)) == NULL)
+ errx(1, "out of memory");
+ if ((gw_conf->got_www_path = strdup(D_GOTWWW)) == NULL)
+ errx(1, "out of memory");
+ if ((gw_conf->got_site_name = strdup(D_SITENAME)) == NULL)
+ errx(1, "out of memory");
+ if ((gw_conf->got_site_owner = strdup(D_SITEOWNER)) == NULL)
+ errx(1, "out of memory");
+ if ((gw_conf->got_site_link = strdup(D_SITELINK)) == NULL)
+ errx(1, "out of memory");
+ if ((gw_conf->got_logo = strdup(D_GOTLOGO)) == NULL)
+ errx(1, "out of memory");
+ if ((gw_conf->got_logo_url = strdup(D_GOTURL)) == NULL)
+ errx(1, "out of memory");
+ gw_conf->got_show_site_owner = D_SHOWSOWNER;
+ gw_conf->got_show_repo_owner = D_SHOWROWNER;
+ gw_conf->got_show_repo_age = D_SHOWAGE;
+ gw_conf->got_show_repo_description = D_SHOWDESC;
+ gw_conf->got_show_repo_cloneurl = D_SHOWURL;
+ gw_conf->got_max_repos = D_MAXREPO;
+ gw_conf->got_max_repos_display = D_MAXREPODISP;
+ gw_conf->got_max_commits_display = D_MAXCOMMITDISP;
+ if ((file = pushfile(filename)) == NULL) {
+ error = got_error_from_errno2("parse_conf", GOTWEB_CONF);
+ goto done;
+ }
+ topfile = file;
+
+ yyparse();
+ popfile();
+ if (gerror)
+ error = gerror;
+done:
+ return error;
+}
blob - da3924e2e27178c8c42cfa8411735cbb75eb814b
blob + 40fa9cc99ceca8d3d1fe298c1a381c45073a6736
--- libexec/Makefile.inc
+++ libexec/Makefile.inc
.include "../Makefile.inc"
+.if ${MAKEWEB} == "Yes"
realinstall:
+ if [ ! -d ${LIBEXEC_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${LIBEXEC_DIR}; \
+ fi
+ ${INSTALL} ${INSTALL_COPY} -o root -g daemon -m 755 ${PROG} \
+ ${LIBEXEC_DIR}/${PROG}
+.else
+realinstall:
${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} \
-m ${BINMODE} ${PROG} ${LIBEXECDIR}/${PROG}
-
+.endif
NOMAN = Yes