1#!/bin/sh
2
3#  * Copyright 2004 Tristan Chabredier <wwp@claws-mail.org>
4#  *
5#  * This file is free software; you can redistribute it and/or modify it
6#  * under the terms of the GNU General Public License as published by
7#  * the Free Software Foundation; either version 3 of the License, or
8#  * (at your option) any later version.
9#  *
10#  * This program is distributed in the hope that it will be useful, but
11#  * WITHOUT ANY WARRANTY; without even the implied warranty of
12#  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13#  * General Public License for more details.
14#  *
15#  * You should have received a copy of the GNU General Public License
16#  * along with this program; if not, write to the Free Software
17#  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18#
19# fix_date.sh		helper script to fix non-standard date or add missing
20#					date header to emails
21
22# usage: fix_date.sh <filename> [<filename> ..]
23# It will replace the Date: value w/ the one picked up from more recent
24# Fetchinfo time header, Received: field.. Otherwise, it will take the file
25#  modification time (using a RFC 2822-compliant form).
26# Any already existing X-Original-Date is kept, if missing we're adding it
27# if the Date: was set (even if set w/ non conform value)
28
29# TODO: fallback to X-OriginalArrivalTime: ?
30
31VERSION="0.1.3"
32
33
34version()
35{
36	echo "$VERSION"
37	exit 0
38}
39
40usage()
41{
42	echo "usage:"
43	echo "  ${0##*/} [<switches>] <filename> [<filename> ..]"
44	echo "switches:"
45	echo "  -h --help     display this help then exit"
46	echo "  -v --version  display version information then exit"
47	echo "  -d --debug    turn on debug information (be more verbose)"
48	echo "  -f --force    always force (re-)writing of Date: header"
49	echo "  -r --rfc      force re-writing of Date: header when it's not RFC-compliant"
50	echo "  -s --strict   use RFC-strict matching patterns for dates"
51	echo "  --            end of switches (in case a filename starts with a -)"
52	exit $1
53}
54
55date_valid()
56{
57	test $STRICT -eq 1 && \
58		REGEXP="$DATE_REGEXP_STRICT" || \
59		REGEXP="$DATE_REGEXP"
60
61	echo "$1" | grep -qEim 1 "$REGEXP"
62	DATE_VALID=$?
63}
64
65dump_date_fields()
66{
67	test -z "$X_ORIGINAL_DATE" -a -n "$DATE" && \
68		echo "X-Original-Date:$DATE" >> "$TMP"
69	echo "Date:$REPLACEMENT_DATE" >> "$TMP"
70}
71
72
73# use --force to always (re-)write the Date header
74# otherwise, the Date header will be written if only it doesn't exist
75FORCE=0
76# use --rfc to (re-)write the Date header when it's not RFC-compliant
77# otherwise, the Date header will be written if only it doesn't exist
78RFC=0
79# use --debug to display more information about what's performed
80DEBUG=0
81# use --strict to use strict matching patterns for date validation
82STRICT=0
83# 0 = valid, always valid until --strict is used, then date_valid overrides this value
84DATE_VALID=0
85# max header lines (300 is a reasonable minimum value but 600 has already been encountered, set to 1000 by security)
86MAX_HEADER_LINES=1000
87
88while [ -n "$1" ]
89do
90	case "$1" in
91	-h|--help)		usage 0;;
92	-v|--version)	version;;
93	-f|--force)		FORCE=1;;
94	-d|--debug)		DEBUG=1;;
95	-r|--rfc)		RFC=1;;
96	-s|--strict)	STRICT=1;;
97	--)				shift
98					break;;
99	-*)				echo "error: unrecognized switch '$1'"
100					usage 1;;
101	*)				break;;
102	esac
103	shift
104done
105
106if [ $FORCE -eq 1 -a $RFC -eq 1 ]
107then
108	echo "error: use either --force or --rfc, but not both at the same time"
109	usage 1
110fi
111
112test $# -lt 1 && \
113	usage 1
114
115TMP="/tmp/${0##*/}.$$.tmp"
116HEADERS="/tmp/${0##*/}.$$.headers.tmp"
117BODY="/tmp/${0##*/}.$$.body.tmp"
118
119DATE_REGEXP='( (Mon|Tue|Wed|Thu|Fri|Sat|Sun),)? [0-9]+ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]+ [0-9]+:[0-9]+:[0-9]+ [+-]?[0-9]+'
120DATE_REGEXP_STRICT='(Mon|Tue|Wed|Thu|Fri|Sat|Sun), [0-9]+ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]+ [0-9]+:[0-9]+:[0-9]+ [+-]?[0-9]+'
121
122while [ -n "$1" ]
123do
124	# skip if file is empty or doesn't exist
125	if [ ! -s "$1" ]
126	then
127		test $DEBUG -eq 1 && \
128			echo "$1: no found or empty, skipping"
129		shift
130		continue
131	fi
132	SKIP=0
133
134	# split headers and body
135	# find the empty line that separates body (if any) from headers,
136	# work on a temporary dos2unix'ed copy because body might
137	# contain DOS CRLF and grep '^$' won't work
138	head -$MAX_HEADER_LINES "$1" | dos2unix > "$TMP"
139	SEP=`grep -nEm1 "^$" "$TMP" 2>/dev/null | cut -d ':' -f 1`
140	rm -f "$TMP"
141	if [ -z "$SEP" -o "$SEP" = "0" -o $? -ne 0 ]
142	then
143		cp -f "$1" "$HEADERS"
144		:> "$BODY"
145		test $DEBUG -eq 1 && \
146			echo "$1: no body part could be found before line $MAX_HEADER_LINES"
147	else
148		sed -n '1,'`expr $SEP - 1`'p' "$1" > "$HEADERS"
149		sed '1,'`expr $SEP - 1`'d' "$1" > "$BODY"
150	fi
151
152	# work on headers only
153
154	# get the Date and X-Original-Date
155	X_ORIGINAL_DATE=`sed -n '/^X-Original-Date:/,/^[^\t]/p' "$HEADERS" | head -n -1 | cut -d ':' -f 2-`
156	DATE=`sed -n '/^Date:/,/^[^\t]/p' "$HEADERS" | head -n -1 | cut -d ':' -f 2-`
157
158	# work on headers, minus Date and X-Original-Date
159	test -n "$X_ORIGINAL_DATE" && \
160		sed -i '/^X-Original-Date:/,/^[^\t]/d' "$HEADERS"
161	test -n "$DATE" && \
162		sed -i '/^Date:/,/^[^\t]/d' "$HEADERS"
163
164	# find a replacement date in Fetchinfo: header
165	FETCH_DATE=`grep -im1 'X-FETCH-TIME: ' "$HEADERS" | cut -d ' ' -f 2-`
166
167	# or in Received: headers ..
168	test $STRICT -eq 1 && \
169		REGEXP="$DATE_REGEXP" || \
170		REGEXP="$DATE_REGEXP_STRICT"
171	RECEIVED_DATE=`sed -n '/^Received:/,/^[^\t]/p' "$HEADERS" | head -n -1 | grep -Eoim 1 "$REGEXP"`
172
173	# .. or from file properties
174	FILE_DATE=`LC_ALL=POSIX LANG=POSIX ls -l --time-style="+%a, %d %b %Y %X %z" "$1" | tr -s ' ' | cut -d ' ' -f 6-11`
175	# we could also use the system date as a possible replacement
176	SYSTEM_DATE="`date -R`"
177
178	# determine which replacement date to use
179	if [ -z "$FETCH_DATE" ]
180	then
181		if [ -z "$RECEIVED_DATE" ]
182		then
183			# don't forget the leading whitespace here
184			REPLACEMENT_DATE=" $FILE_DATE"
185			REPLACEMENT="file date"
186#			REPLACEMENT_DATE=" $SYSTEM_DATE"
187#			REPLACEMENT="system date"
188		else
189			REPLACEMENT_DATE="$RECEIVED_DATE"
190			REPLACEMENT="received date"
191		fi
192	else
193		# don't forget the leading whitespace here
194		REPLACEMENT_DATE=" $FETCH_DATE"
195		REPLACEMENT="Fetchinfo time header"
196	fi
197
198	# ensure that the original X-Original-Date is kept
199	:> "$TMP"
200	if [ -n "$X_ORIGINAL_DATE" ]
201	then
202		echo "X-Original-Date:$X_ORIGINAL_DATE" >> "$TMP"
203	fi
204
205	# replace/set the date and write all lines
206	test $RFC -eq 1 && \
207		date_valid "$DATE"
208	if [ -z "$DATE" ]
209	then
210		test $DEBUG -eq 1 && \
211			echo "$1: date not found, using $REPLACEMENT now"
212		dump_date_fields
213	else
214		if [ $FORCE -eq 1 ]
215		then
216			test $DEBUG -eq 1 && \
217				echo "$1: date already found, replacing with $REPLACEMENT"
218			dump_date_fields
219		else
220			if [ $RFC -eq 1 ]
221			then
222				if [ $DATE_VALID -ne 0 ]
223				then
224					test $DEBUG -eq 1 && \
225						echo "$1: date already found but not RFC-compliant, replacing with $REPLACEMENT"
226					dump_date_fields
227				else
228					test $DEBUG -eq 1 && \
229						echo "$1: date already found and RFC-compliant, skipping"
230					SKIP=1
231				fi
232			else
233				test $DEBUG -eq 1 && \
234					echo "$1: date already found, skipping"
235				SKIP=1
236			fi
237		fi
238	fi
239
240	if [ $SKIP -eq 0 ]
241	then
242		# uncomment the following line to backup the original file
243		#mv -f "$1" "$1.bak"
244
245		cat "$HEADERS" >> "$TMP"
246		cat "$BODY" >> "$TMP"
247		mv -f "$TMP" "$1"
248		if [ $? -ne 0 ]
249		then
250			echo "error while moving '$TMP' to '$1'"
251			exit 1
252		fi
253	fi
254	rm -f "$HEADERS" "$BODY" "$TMP" >/dev/null 2>&1
255
256	shift
257done
258exit 0
259