jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

commit 8e0a1f6357238f9de4b064385317e0865be2f74e
parent be457efe87a88b1168c74437ea38d1ec50f0da97
Author: Jaromil <jaromil@dyne.org>
Date:   Wed, 23 Jul 2014 22:32:50 +0200

fixes to parse email date in publish function

Diffstat:
Mbuild/build-gnu.sh | 148++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Asrc/parsedate.c | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zlibs/publish | 233+++++++++++++++++++++++++++++++++++++++++++------------------------------------
3 files changed, 310 insertions(+), 175 deletions(-)

diff --git a/build/build-gnu.sh b/build/build-gnu.sh @@ -17,75 +17,75 @@ target=all mkdir -p build/gnu -{ test "$target" = "deps" } || { +{ test "$target" = "deps" } || { test "$target" = "all" } && { deps=() case $distro in - debian) - print "Building on Debian" - print "Checking software to install" - which procmail >/dev/null || deps+=(procmail) - which fetchmail >/dev/null || deps+=(fetchmail) - which msmtp >/dev/null || deps+=(msmtp) - which mutt >/dev/null || deps+=(mutt) - which mairix >/dev/null || deps+=(mairix) - which pinentry >/dev/null || deps+=(pinentry-curses) - which abook >/dev/null || deps+=(abook) - which wipe >/dev/null || deps+=(wipe) - - print "Checking build dependencies" - which gcc >/dev/null || deps+=(gcc) - which bison >/dev/null || deps+=(bison) - which flex >/dev/null || deps+=(flex) - which make >/dev/null || deps+=(make) - which autoconf >/dev/null || deps+=(autoconf) - which automake >/dev/null || deps+=(automake) - which sqlite3 >/dev/null || deps+=(sqlite3) -# which gpgme-config || sudo apt-get install libgpgme11-dev - - { test -r /usr/share/doc/libgnome-keyring-dev/copyright } || { - deps+=(libglib2.0-dev libgnome-keyring-dev) } - - - { test ${#deps} -gt 0 } && { - print "Installing missing components" - sudo apt-get install ${=deps} } - - ;; - - fedora) - - print "Building on Fedora" - print "Checking software to install..." - which zsh || sudo yum install zsh - which mutt || sudo yum install mutt - which procmail || sudo yum install procmail - which msmtp || sudo yum install msmtp - which pinentry || sudo yum install pinentry - which fetchmail || sudo yum install fetchmail - which wipe || sudo yum install wipe - which abook || sudo yum install abook - - print "Checking build dependencies" - which gcc || sudo yum install gcc - which bison || sudo yum install bison - which flex || sudo yum install flex - rpm -q glib2-devel || sudo yum install glib2-devel - rpm -q libgnome-keyring-devel || sudo yum install libgnome-keyring-devel - rpm -q bzip2-devel || sudo yum install bzip2-devel - rpm -q zlib-devel || sudo yum install zlib-devel - - ;; - - *) - print "Error: no distro recognized, build by hand." - ;; + debian) + print "Building on Debian" + print "Checking software to install" + which procmail >/dev/null || deps+=(procmail) + which fetchmail >/dev/null || deps+=(fetchmail) + which msmtp >/dev/null || deps+=(msmtp) + which mutt >/dev/null || deps+=(mutt) + which mairix >/dev/null || deps+=(mairix) + which pinentry >/dev/null || deps+=(pinentry-curses) + which abook >/dev/null || deps+=(abook) + which wipe >/dev/null || deps+=(wipe) + + print "Checking build dependencies" + which gcc >/dev/null || deps+=(gcc) + which bison >/dev/null || deps+=(bison) + which flex >/dev/null || deps+=(flex) + which make >/dev/null || deps+=(make) + which autoconf >/dev/null || deps+=(autoconf) + which automake >/dev/null || deps+=(automake) + which sqlite3 >/dev/null || deps+=(sqlite3) +# which gpgme-config || sudo apt-get install libgpgme11-dev + + { test -r /usr/share/doc/libgnome-keyring-dev/copyright } || { + deps+=(libglib2.0-dev libgnome-keyring-dev) } + + + { test ${#deps} -gt 0 } && { + print "Installing missing components" + sudo apt-get install ${=deps} } + + ;; + + fedora) + + print "Building on Fedora" + print "Checking software to install..." + which zsh || sudo yum install zsh + which mutt || sudo yum install mutt + which procmail || sudo yum install procmail + which msmtp || sudo yum install msmtp + which pinentry || sudo yum install pinentry + which fetchmail || sudo yum install fetchmail + which wipe || sudo yum install wipe + which abook || sudo yum install abook + + print "Checking build dependencies" + which gcc || sudo yum install gcc + which bison || sudo yum install bison + which flex || sudo yum install flex + rpm -q glib2-devel || sudo yum install glib2-devel + rpm -q libgnome-keyring-devel || sudo yum install libgnome-keyring-devel + rpm -q bzip2-devel || sudo yum install bzip2-devel + rpm -q zlib-devel || sudo yum install zlib-devel + + ;; + + *) + print "Error: no distro recognized, build by hand." + ;; esac - + print "All dependencies installed" } -{ test "$target" = "fetchaddr" } || { +{ test "$target" = "fetchaddr" } || { test "$target" = "all" } && { pushd src print -n "Compiling the address parser... " @@ -100,15 +100,25 @@ mkdir -p build/gnu } { test "$target" = "dfasyn" } || { - test "$target" = "all" } && { + test "$target" = "all" } && { print "Compiling the generator for deterministic finite state automata... " pushd src/dfasyn make popd } +{ test "$target" = "parsedate" } || { + test "$target" = "all" } && { + print "Compiling the minimalistic RFC822 date re-formatter..." + pushd src + ${=cc} -o parsedate parsedate.c + popd + cp src/parsedate build/gnu/ + print OK +} + { test "$target" = "fetchdate" } || { - test "$target" = "all" } && { + test "$target" = "all" } && { print "Compiling the date parser... " pushd src # then the C files made by dfasyn @@ -122,21 +132,21 @@ mkdir -p build/gnu cp src/fetchdate build/gnu/ print OK } - -{ test "$target" = "gnome-keyring" } || { + +{ test "$target" = "gnome-keyring" } || { test "$target" = "all" } && { print "Compiling gnome-keyring" pushd src/gnome-keyring ${=cc} jaro-gnome-keyring.c -o jaro-gnome-keyring \ - `pkg-config --cflags --libs glib-2.0 gnome-keyring-1` + `pkg-config --cflags --libs glib-2.0 gnome-keyring-1` popd cp src/gnome-keyring/jaro-gnome-keyring build/gnu/ } # build mixmaster only if specified -{ test "$target" = "mixmaster" } && { +{ test "$target" = "mixmaster" } && { print "Compiling Mixmaster (anonymous remailer)" pushd src/mixmaster-3.0/Src mixmaster_sources=(main menustats mix rem rem1 rem2 chain chain1 chain2 nym) @@ -144,7 +154,7 @@ mkdir -p build/gnu mixmaster_sources+=(compress stats crypto random rndseed util buffers maildir parsedate.tab) bison parsedate.y for s in ${=mixmaster_sources}; do ${=cc} -c ${s}.c; done - ${=cc} -o mixmaster *.o -lssl + ${=cc} -o mixmaster *.o -lssl popd cp src/mixmaster-3.0/Src/mixmaster build/gnu } diff --git a/src/parsedate.c b/src/parsedate.c @@ -0,0 +1,104 @@ +/* Jaro Mail + * + * (C) Copyright 2014 Denis Roio <jaromil@dyne.org> + * + * Minimalist date reformatted learned from Mairix and Mutt + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Public License as published + * by the Free Software Foundation; either version 3 of the License, + * or (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * Please refer to the GNU Public License for more details. + * + * You should have received a copy of the GNU Public License along with + * this source code; if not, write to: + * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +/* #include <sys/types.h> */ +/* #include <sys/stat.h> */ + +time_t parse_rfc822_date(char *date_string)/*{{{*/ +{ + struct tm tm; + char *s, *z; + /* Format [weekday ,] day-of-month month year hour:minute:second timezone. + + Some of the ideas, sanity checks etc taken from parse.c in the mutt + sources, credit to Michael R. Elkins et al + */ + + s = date_string; + z = strchr(s, ','); + if (z) s = z + 1; + while (*s && isspace(*s)) s++; + /* Should now be looking at day number */ + if (!isdigit(*s)) goto tough_cheese; + tm.tm_mday = atoi(s); + if (tm.tm_mday > 31) goto tough_cheese; + + while (isdigit(*s)) s++; + while (*s && isspace(*s)) s++; + if (!*s) goto tough_cheese; + if (!strncasecmp(s, "jan", 3)) tm.tm_mon = 0; + else if (!strncasecmp(s, "feb", 3)) tm.tm_mon = 1; + else if (!strncasecmp(s, "mar", 3)) tm.tm_mon = 2; + else if (!strncasecmp(s, "apr", 3)) tm.tm_mon = 3; + else if (!strncasecmp(s, "may", 3)) tm.tm_mon = 4; + else if (!strncasecmp(s, "jun", 3)) tm.tm_mon = 5; + else if (!strncasecmp(s, "jul", 3)) tm.tm_mon = 6; + else if (!strncasecmp(s, "aug", 3)) tm.tm_mon = 7; + else if (!strncasecmp(s, "sep", 3)) tm.tm_mon = 8; + else if (!strncasecmp(s, "oct", 3)) tm.tm_mon = 9; + else if (!strncasecmp(s, "nov", 3)) tm.tm_mon = 10; + else if (!strncasecmp(s, "dec", 3)) tm.tm_mon = 11; + else goto tough_cheese; + + while (!isspace(*s)) s++; + while (*s && isspace(*s)) s++; + if (!isdigit(*s)) goto tough_cheese; + tm.tm_year = atoi(s); + if (tm.tm_year < 70) { + tm.tm_year += 100; + } else if (tm.tm_year >= 1900) { + tm.tm_year -= 1900; + } + + while (isdigit(*s)) s++; + while (*s && isspace(*s)) s++; + if (!*s) goto tough_cheese; + + /* Now looking at hms */ + /* For now, forget this. The searching will be vague enough that nearest day is good enough. */ + + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + tm.tm_isdst = 0; + return mktime(&tm); + +tough_cheese: + return (time_t) -1; /* default value */ +} + +int main(int argc, char **argv) { + time_t res; + if(argc<2) { + printf("usage: parsedate date_string_from_mail_header\n"); + printf("returns date in seconds since the Epoch.\n"); + exit(0); } + res = parse_rfc822_date(argv[1]); + if(res<0) exit(1); + // printf("Date: %d (seconds since the Epoch)\n",res); + printf("%lld\n", (long long) res); + exit(0); +} diff --git a/src/zlibs/publish b/src/zlibs/publish @@ -31,8 +31,8 @@ pubdb="" pubdb_create() { func "create PubDB" { test -r "$1" } && { - error "PubDBalready exists: $1" - return 1 + error "PubDBalready exists: $1" + return 1 } cat <<EOF | ${SQL} -batch "$1" CREATE TABLE published @@ -44,8 +44,8 @@ CREATE TABLE published ); EOF { test $? != 0 } && { - error "Error creating PubDB in $1" - return 1 } + error "Error creating PubDB in $1" + return 1 } # make sure is private chmod 600 "$1" chown $_uid:$_gid "$1" @@ -69,11 +69,11 @@ pubdb_insert() { func "insert mail in pubdb: $_path" cat <<EOF | ${SQL} -batch "$pubdb" INSERT INTO published (uid, file, path, date) -VALUES ("${uid}", "${_file}", "${_path}", "`pubdb_date`"); +VALUES ("${uid}", "${_file}", "${_path}", "`pubdb_date ${_path}`"); EOF { test $? != 0 } && { - error "insert_mail: duplicate insert in $pubdb" - return 1 + error "insert_mail: duplicate insert in $pubdb" + return 1 } return 0 } @@ -83,14 +83,18 @@ pubdb_list() { cat <<EOF | ${SQL} -batch "$pubdb" .width 64 128 SELECT path FROM published -ORDER BY file DESC; +ORDER BY date DESC; EOF } pubdb_getuid() { func "pubdb_getuid() $@" + # TODO: path should be file only, find through all maildir _path="$1" - { test -r "$_path" } || { error "path not found for getuid: $_path"; return 1 } + { test -r "$_path" } || { + error "path not found for getuid: $_path" + # TODO: remove from pubdb + return 1 } uid=`hdr "$_path" | awk '/^Message-ID:/ { gsub(/<|>|,/ , "" , $2); print $2 }'` uid="${(Q)uid%\\n*}" # trim { test "$uid" = "" } && { uid=`basename "$_path"` } @@ -98,20 +102,38 @@ pubdb_getuid() { } +# Takes an email file, parse headers and returns an RFC3339 formatted +# date value that is suitable for both RSS and SQLITE use. Uses the +# small C program parsedate for RFC822 parsing (borrowed from Mairix) pubdb_date() { + _path="$1" + { test -r "$_path" } || { + error "path not found for pubdb_date: $_path" + return 1 } + _datestring=`hdr "$_path" | awk '/^Date/ { print $0 }'` + func "pubdb_date parsed string: $_datestring" + _dateseconds=`${WORKDIR}/bin/parsedate "$_datestring"` + { test "$_dateseconds" = "-1" } && { + error "could not parse date: $_datestring" + return 1 } + func "pubdb_date parsed seconds: $_dateseconds" + _daterss=`date -d"@${_dateseconds}" --rfc-3339=seconds | sed 's/ /T/'` + print "${_daterss}" + return 0 + # ATOM spec wants a T where date puts a space - date --rfc-3339=seconds | sed 's/ /T/' + # date --rfc-3339=seconds | sed 's/ /T/' } pubdb_update() { func "pubdb_update() $@" md="$1" { test "$md" = "" } && { - error "Pubdb_update: maildir not found: $md" - return 1 } + error "Pubdb_update: maildir not found: $md" + return 1 } { maildircheck "${md}" } || { - error "Pubdb_update: not a maildir: $md" - return 1 } + error "Pubdb_update: not a maildir: $md" + return 1 } pub="${md}/pub"; ${=mkdir} "$pub" pubdb="${md}/.pubdb" { test -r "$pubdb" } || { pubdb_create "$pubdb" } @@ -120,13 +142,13 @@ pubdb_update() { mails=`${=find} "${md}/new" "${md}/cur" "${md}/tmp" -type f` notice "Publishing emails in $md" for m in ${(f)mails}; do - pubdb_getuid "$m" - u=`pubdb_lookup_uid "$uid"` - { test "$u" = "" } && { - # $u is a new message - func "publish_rss: new message found with uid: $uid" - pubdb_insert "$m" - } + pubdb_getuid "$m" + u=`pubdb_lookup_uid "$uid"` + { test "$u" = "" } && { + # $u is a new message + func "publish_rss: new message found with uid: $uid" + pubdb_insert "$m" + } done return 0 } @@ -150,68 +172,68 @@ pubdb_extract_body() { # check if it has already html _html=`mu extract "$_path" | awk '/text\/html/ {print $1; exit}'` { test "$_html" = "" } || { - mu extract --overwrite --parts="$_html" "$_path" - # check if there is an html header to weed out - grep '<body>' "$_html".part > /dev/null - if [ $? = 0 ]; then - awk ' + mu extract --overwrite --parts="$_html" "$_path" + # check if there is an html header to weed out + grep '<body>' "$_html".part > /dev/null + if [ $? = 0 ]; then + awk ' BEGIN { body=0 } /<body/ { body=1; next } /<\/body/ { body=0; next } { if(body==1) print $0 }' "$_html".part | iconv -c - else - cat "$_html".part | iconv -c - fi - rm "$_html".part - return 0 } + else + cat "$_html".part | iconv -c + fi + rm "$_html".part + return 0 } # use the first text/plain _text=`mu extract "$_path" | awk '/text\/plain/ {print $1; exit}'` { test "$_text" = "" } || { - mu extract --overwrite --parts="$_text" "$_path" - # here we tweak the origin to avoid headers in markdown - # preferring to interpret # as inline preformat - cat "$_text".part | sed ' + mu extract --overwrite --parts="$_text" "$_path" + # here we tweak the origin to avoid headers in markdown + # preferring to interpret # as inline preformat + cat "$_text".part | sed ' s/^#/ /g -' | iconv -c | maruku --html-frag | sed ' +' | iconv -c | maruku --html-frag 2>/dev/null | sed ' s|http://[^ |<]*|<a href="&">&</a>|g s|https://[^ |<]*|<a href="&">&</a>|g' # s|www\.[^ |<]*|<a href="http://&">&</a>|g' - rm "$_text".part - return 0 + rm "$_text".part + return 0 } # check if its an html only mail # _html=`mu extract "$_path" | awk '/text\/html/ {print $1; exit}'` # { test "$_html" = "" } || { - # mu extract --overwrite --parts="$_html" "$_path" - # elinks -dump "$_html".part - # rm "$_html".part - # return 0 } + # mu extract --overwrite --parts="$_html" "$_path" + # elinks -dump "$_html".part + # rm "$_html".part + # return 0 } return 0 } - - + + # iterate through the pubdb and publish an rss pubdb_render_maildir() { func "publish_render_maildir() $@" md="$1" { test "$md" = "" } && { - error "Publish_render_maildir: not found: $md" - return 1 } + error "Publish_render_maildir: not found: $md" + return 1 } { maildircheck "${md}" } || { - error "Publish_render_maildir: not a maildir: $md" - return 1 } + error "Publish_render_maildir: not a maildir: $md" + return 1 } { test -r "${md}/pub" } || { - error "Publish_render_maildir: webnomad not found in ${md}" - error "Initialize Webnomad inside the maildir that you want published." - return 1 } + error "Publish_render_maildir: webnomad not found in ${md}" + error "Initialize Webnomad inside the maildir that you want published." + return 1 } pub="${md}/pub" pubdb="${md}/.pubdb" { test -r "$pubdb" } || { - error "Publish_render_maildir: first run update_pubdb for $md"; return 1 } + error "Publish_render_maildir: first run update_pubdb for $md"; return 1 } ${=mkdir} $TMPDIR/pubdb @@ -230,7 +252,7 @@ pubdb_render_maildir() { <link href="${WEB_ROOT}" /> <id>${WEB_ROOT}/atom.xml</id> -<updated>`pubdb_date`</updated> +<updated>`date --rfc-3339=seconds | sed 's/ /T/'`</updated> <generator uri="http://www.dyne.org/software/jaro-mail/">JaroMail</generator> <subtitle type="html">${DESCRIPTION}</subtitle> @@ -251,88 +273,88 @@ EOF c=0 for m in ${(f)mails}; do - # fill in uid and upath - pubdb_getuid "$m" + # fill in uid and upath + pubdb_getuid "$m" - # but skip entries no more existing in maildir - { test -r "$m" } || { continue } - # TODO: remove $m from database if not in maildir + # but skip entries no more existing in maildir + { test -r "$m" } || { continue } + # TODO: remove $m from database if not in maildir - _from=`awk '/^From: / {print $0; exit}' "$m" | sed 's/^From: //; s/ *<[^>]*> *//; s/"//g'` - _to=`awk '/^To: / {print $0; exit}' "$m" | sed 's/^To: //; s/ *<[^>]*> *//; s/"//g'` + _from=`awk '/^From: / {print $0; exit}' "$m" | sed 's/^From: //; s/ *<[^>]*> *//; s/"//g'` + _to=`awk '/^To: / {print $0; exit}' "$m" | sed 's/^To: //; s/ *<[^>]*> *//; s/"//g'` - if [[ "$_from" =~ "=?UTF-8" ]]; then - _fname=`print "${_from%?=}" | sed 's/^=?UTF-8?B?//' | base64 -d` - else _fname="${_from}"; fi + if [[ "$_from" =~ "=?UTF-8" ]]; then + _fname=`print "${_from%?=}" | sed 's/^=?UTF-8?B?//' | base64 -d` + else _fname="${_from}"; fi - if [[ "$_to" =~ "=?UTF-8" ]]; then - _tname=`print "${_to%?=}" | sed 's/^=?UTF-8?B?//' | base64 -d` - else _tname="${_to}"; fi + if [[ "$_to" =~ "=?UTF-8" ]]; then + _tname=`print "${_to%?=}" | sed 's/^=?UTF-8?B?//' | base64 -d` + else _tname="${_to}"; fi # _from=`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x From -a` # _to=`hdr "$m" | ${WORKDIR}/bin/fetchaddr -x To -a` # _fname=`print ${(Q)_from[(ws:,:)2]} | iconv -c` # _tname=`print ${(Q)_to[(ws:,:)2]} | iconv -c` - func "From: ${_fname}" - _subject=`hdr "$m" | awk ' + func "From: ${_fname}" + _subject=`hdr "$m" | awk ' /^Subject:/ { for(i=2;i<=NF;i++) printf "%s ", $i; printf "\n" } ' | pubdb_escape_html` - _date=`hdr "$m" | awk '/^Date:/ { print $0 }'` - # fill in uid and upath - pubdb_getuid "$m" + # fill in uid and upath + pubdb_getuid "$m" + + # fill in the body + _body=`pubdb_extract_body $m` - # fill in the body - _body=`pubdb_extract_body $m` + { test "$_body" = "" } && { error "Error rendering $m" } - { test "$_body" = "" } && { error "Error rendering $m" } - - (( ++c )) - if (( $c < ${FEED_LIMIT:=30} )); then - - # write out the atom entry - cat <<EOF >> "$pub"/atom.xml + (( ++c )) + if (( $c < ${FEED_LIMIT:=30} )); then + + # write out the atom entry + cat <<EOF >> "$pub"/atom.xml <entry> - <title type="html" xml:lang="en-US">$_subject</title> - <link href="${WEB_ROOT}${upath}" /> - <id>${WEB_ROOT}${upath}</id> - <updated>`pubdb_date`</updated> + <title type="html" xml:lang="en-US">$_subject</title> + <link href="${WEB_ROOT}${upath}" /> + <id>${WEB_ROOT}${upath}</id> + <updated>`pubdb_date "$m"`</updated> <content type="html" xml:lang="en-US"> `print ${(f)_body} | pubdb_escape_html` </content> <author> - <name>${_fname}</name> - <uri>${WEB_ROOT}${upath}</uri> + <name>${_fname}</name> + <uri>${WEB_ROOT}${upath}</uri> </author> <source> - <title type="html">${_subject}</title> - <subtitle type="html">From: ${_fname}</subtitle> - <updated>`pubdb_date`</updated> - <link rel="self" href="${WEB_ROOT}${upath}" /> - <id>${WEB_ROOT}${upath}</id> + <title type="html">${_subject}</title> + <subtitle type="html">From: ${_fname}</subtitle> + <updated>${_daterss}</updated> + <link rel="self" href="${WEB_ROOT}${upath}" /> + <id>${WEB_ROOT}${upath}</id> </source> </entry> EOF - - fi # FEED LIMIT not reached - - ####### - # now build an index and the sitemap - - - # if using webnomad write out also the message page - { test -d "${md}/views" } && { - cat <<EOF > "${md}/views/${upath}" + + fi # FEED LIMIT not reached + + ####### + # now build an index and the sitemap + + + # if using webnomad write out also the message page + { test -d "${md}/views" } && { + _datestring=`hdr "$_path" | awk '/^Date/ { print $0 }'` + cat <<EOF > "${md}/views/${upath}" <h2>${_subject}</h2> -<h4>From: ${_fname} - sent to ${_tname} - ${_date}</h4> +<h4>From: ${_fname} - sent to ${_tname} - ${_datestring}</h4> ${_body} EOF - # add entry in index - cat <<EOF >> "${md}/views/index.html" + # add entry in index + cat <<EOF >> "${md}/views/index.html" <tr> <td style="vertical-align:middle;"><a href="${WEB_ROOT}${upath}">${_fname}</a></td> @@ -342,7 +364,7 @@ EOF </tr> EOF - } + } done # loop is over cat <<EOF >> "${pub}/atom.xml" @@ -354,4 +376,3 @@ EOF EOF } -