about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--TODO1
-rw-r--r--config.example.h16
-rw-r--r--doc/man/man1/sternenblog.cgi.18
-rw-r--r--main.c134
-rw-r--r--templates/simple.c51
6 files changed, 172 insertions, 40 deletions
diff --git a/Makefile b/Makefile
index 4909d6d..97529d6 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ sternenblog.cgi: xml.o entry.o index.o stringutil.o cgiutil.o timeutil.o $(TEMPL
 main.o: main.c core.h timeutil.h config.h
 	$(CC) $(CFLAGS) -c -o main.o $<
 
-$(TEMPLATE).o: $(TEMPLATE).c core.h config.h xml.h cgiutil.h timeutil.h
+$(TEMPLATE).o: $(TEMPLATE).c core.h config.h xml.h cgiutil.h timeutil.h stringutil.h
 	$(CC) $(CFLAGS) -I$(ROOT_DIR) -c -o $@ $<
 
 entry.o: config.h entry.c
diff --git a/TODO b/TODO
index f4e9b92..653446f 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,4 @@
 make RSS_TTL optional | id:32a848e82d8b39bb8495f49d491882da320a3c7d
-atom feed | id:44aca4a04ff19c567e08968370cc535139807bbd
 add support for caching | id:472d68f1af5489020f4c5808e805822e3da77ffc
 rethink logging / debug output | id:5734e2717f28d80ac65d3f2123a0f46c01d4a0c1
 if possible determine BLOG_SERVER_URL automatically | id:5ad1fabab42a5776ab1446d09659c7a99a104245
diff --git a/config.example.h b/config.example.h
index dad9dad..850a2ca 100644
--- a/config.example.h
+++ b/config.example.h
@@ -54,10 +54,24 @@
 #define BLOG_TITLE "sternenblog"
 
 /*!
+ * @brief Site's Author
+ *
+ * Name of the author of the site served.
+ * Used to set the feed's author in the atom feed,
+ * ignored for RSS (since it is not recommended to
+ * use for single author feeds there which is the only type
+ * sternenblog can handle).
+ *
+ * Optional setting, if missing replaced by the username
+ * sternenblog is running as.
+ */
+#define BLOG_AUTHOR "Jane Doe"
+
+/*!
  * @brief Site description
  *
  * Description of the site to serve.
- * Will be used for the RSS feed and may be utilized by templates.
+ * Will be used for the feeds and may be utilized by templates.
  *
  * @see https://cyber.harvard.edu/rss/rss.html#requiredChannelElements
  */
diff --git a/doc/man/man1/sternenblog.cgi.1 b/doc/man/man1/sternenblog.cgi.1
index 10df672..48c1bee 100644
--- a/doc/man/man1/sternenblog.cgi.1
+++ b/doc/man/man1/sternenblog.cgi.1
@@ -18,10 +18,12 @@ The Request URL
 .Ql /sternenblog.cgi
 results in the index page being served,
 .Ql /sternenblog.cgi/rss.xml
-returns a RSS feed with the same contents as the index page and
-.Ql /sternenblog.cgi/my-entry
+returns a RSS feed with the same contents as the index page, likewise
+.Ql /sternenblog.cgi/atom.xml
+an atom feed and finally
+.Ql /sternenblog.cgi/<my-entry>
 serves the single entry page for
-.Pa /path/to/entry/directory/my-entry .
+.Pa /path/to/entry/directory/<my-entry> .
 .Pp
 Every entry is a regular file in the configured entry directory that meets
 certain criteria (see
diff --git a/main.c b/main.c
index b180d3d..2636236 100644
--- a/main.c
+++ b/main.c
@@ -77,11 +77,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <dirent.h>
+#include <pwd.h>
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <string.h>
 #include <errno.h>
 #include <time.h>
+#include <unistd.h>
 
 #include "core.h"
 #include "config.h"
@@ -128,10 +130,21 @@ void blog_entry(char script_name[], char path_info[]);
  * This function is called if `PATH_INFO` is `/rss.xml`.
  *
  * @see make_index
+ * @see blog_atom
  */
 void blog_rss(char script_name[]);
 
 /*!
+ * @brief Outputs the CGI response for the blog's Atom feed
+ *
+ * This function is called if `PATH_INFO` is `/atom.xml`.
+ *
+ * @see make_index
+ * @see blog_rss
+ */
+void blog_atom(char script_name[]);
+
+/*!
  * @brief Implements routing of requests
  *
  * @see blog_index
@@ -162,8 +175,7 @@ int main(void) {
     } else if(strcmp(path_info, "/rss.xml") == 0) {
         blog_rss(script_name);
     } else if(strcmp(path_info, "/atom.xml") == 0) {
-        // TODO
-        return EXIT_FAILURE;
+        blog_atom(script_name);
     } else {
         blog_entry(script_name, path_info);
     }
@@ -349,3 +361,121 @@ void blog_rss(char script_name[]) {
 
     del_xml_context(&ctx);
 }
+
+void blog_atom(char script_name[]) {
+    struct entry *entries = NULL;
+
+    int count = make_index(BLOG_DIR, script_name, 1, &entries);
+
+    if(count < 0) {
+        send_header("Status", http_status_line(500));
+        send_header("Content-type", "text/plain");
+        terminate_headers();
+
+        puts("Internal Server Error");
+
+        free_index(&entries, count);
+        return;
+    }
+
+    struct xml_context ctx;
+    new_xml_context(&ctx);
+
+    char *self_url = catn_alloc(3, BLOG_SERVER_URL, script_name, "/atom.xml");
+    char *html_url = catn_alloc(2, BLOG_SERVER_URL, script_name);
+
+    send_header("Status", http_status_line(200));
+    send_header("Content-type", "application/atom+xml");
+    terminate_headers();
+
+    xml_raw(&ctx, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+    xml_open_tag_attrs(&ctx, "feed", 1, "xmlns", "http://www.w3.org/2005/Atom");
+
+    xml_open_tag(&ctx, "title");
+    xml_escaped(&ctx, BLOG_TITLE);
+    xml_close_tag(&ctx, "title");
+
+    if(self_url != NULL) {
+        xml_open_tag(&ctx, "id");
+        xml_escaped(&ctx, self_url);
+        xml_close_tag(&ctx, "id");
+
+        xml_empty_tag(&ctx, "link", 2, "rel", "self", "href", self_url);
+        free(self_url);
+    }
+
+    if(html_url != NULL) {
+        xml_empty_tag(&ctx, "link", 3, "rel", "alternate", "type", "text/html", "href", html_url);
+        // freed after author element
+    }
+
+    xml_open_tag(&ctx, "author");
+    xml_open_tag(&ctx, "name");
+#ifdef BLOG_AUTHOR
+    xml_escaped(&ctx, BLOG_AUTHOR);
+#else
+    struct passwd *user;
+    uid_t uid = geteuid();
+    user = getpwuid(uid);
+    xml_escaped(&ctx, user->pw_name);
+#endif
+    xml_close_tag(&ctx, "name");
+
+    if(html_url != NULL) {
+        xml_open_tag(&ctx, "uri");
+        xml_escaped(&ctx, html_url);
+        xml_close_tag(&ctx, "uri");
+        free(html_url);
+    }
+
+    xml_close_tag(&ctx, "author");
+
+    if(count > 0) {
+        time_t update_time = entries[0].time;
+        char strtime_update[MAX_TIMESTR_SIZE];
+        if(flocaltime(strtime_update, ATOM_TIME_FORMAT, MAX_TIMESTR_SIZE, &update_time) > 0) {
+            xml_open_tag(&ctx, "updated");
+            xml_escaped(&ctx, strtime_update);
+            xml_close_tag(&ctx, "updated");
+        }
+    }
+
+    for(int i = 0; i < count; i++) {
+        xml_open_tag(&ctx, "entry");
+
+        xml_open_tag(&ctx, "id");
+        xml_escaped(&ctx, BLOG_SERVER_URL);
+        xml_escaped(&ctx, entries[i].link);
+        xml_close_tag(&ctx, "id");
+
+        xml_open_tag(&ctx, "title");
+        xml_escaped(&ctx, entries[i].title);
+        xml_close_tag(&ctx, "title");
+
+        char strtime_entry[MAX_TIMESTR_SIZE];
+        if(flocaltime(strtime_entry, ATOM_TIME_FORMAT, MAX_TIMESTR_SIZE, &entries[i].time) > 0) {
+            xml_open_tag(&ctx, "updated");
+            xml_escaped(&ctx, strtime_entry);
+            xml_close_tag(&ctx, "updated");
+        }
+
+        char *entry_url = catn_alloc(2, BLOG_SERVER_URL, entries[i].link);
+        if(entry_url != NULL) {
+            xml_empty_tag(&ctx, "link", 3, "rel", "alternate", "type", "text/html", "href", entry_url);
+            free(entry_url);
+        }
+
+        xml_open_tag_attrs(&ctx, "content", 1, "type", "html");
+        xml_open_cdata(&ctx);
+        xml_raw(&ctx, entries[i].text);
+        xml_close_cdata(&ctx);
+        xml_close_tag(&ctx, "content");
+
+        xml_close_tag(&ctx, "entry");
+    }
+
+    xml_close_tag(&ctx, "feed");
+
+    free_index(&entries, count);
+    del_xml_context(&ctx);
+}
diff --git a/templates/simple.c b/templates/simple.c
index 06d69e8..eeee6db 100644
--- a/templates/simple.c
+++ b/templates/simple.c
@@ -7,40 +7,12 @@
 #include <template.h>
 #include <config.h>
 #include <cgiutil.h>
+#include <stringutil.h>
 #include <timeutil.h>
 #include <xml.h>
 
 static struct xml_context ctx;
 
-int make_link(char *buf, size_t size, char *abs_path) {
-    // TODO pass to template somehow?
-    char *script_name = getenv("SCRIPT_NAME");
-
-    if(script_name == NULL || buf == NULL || abs_path == NULL) {
-        return -1;
-    }
-
-    size_t script_name_len = strlen(script_name);
-    size_t abs_path_len = strlen(abs_path);
-
-    size_t min_buf_len = script_name_len + abs_path_len + 1;
-
-    if(min_buf_len > size) {
-        return -1;
-    }
-
-    if(script_name_len > 0) {
-        memcpy(buf, script_name, script_name_len);
-    }
-    if(abs_path_len > 0) {
-        memcpy(buf + script_name_len, abs_path, abs_path_len);
-    }
-
-    buf[script_name_len + abs_path_len] = '\0';
-
-    return min_buf_len;
-}
-
 void output_entry_time(struct xml_context *ctx, struct entry entry) {
     char strtime[MAX_TIMESTR_SIZE];
 
@@ -90,11 +62,26 @@ void template_footer(void) {
 
     xml_open_tag(&ctx, "footer");
 
-    char rss_link[256];
-    if(make_link(rss_link, sizeof rss_link, "/rss.xml") != -1) {
+    char *script_name = getenv("SCRIPT_NAME");
+    char *rss_link = catn_alloc(2, script_name, "/rss.xml");
+    char *atom_link = catn_alloc(2, script_name, "/atom.xml");
+
+    if(rss_link != NULL) {
         xml_open_tag_attrs(&ctx, "a", 1, "href", rss_link);
-        xml_raw(&ctx, "RSS Feed");
+        xml_escaped(&ctx, "RSS Feed");
         xml_close_tag(&ctx, "a");
+
+        free(rss_link);
+    }
+
+    if(atom_link != NULL) {
+        xml_raw(&ctx, " &bull; ");
+
+        xml_open_tag_attrs(&ctx, "a", 1, "href", atom_link);
+        xml_escaped(&ctx, "Atom Feed");
+        xml_close_tag(&ctx, "a");
+
+        free(atom_link);
     }
 
     xml_close_all(&ctx);