1/* Header rename functions -*- mfl -*- 2 Copyright (C) 2010-2021 Sergey Poznyakoff 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 3, or (at your option) 7 any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17module 'header_rename'. 18require status 19 20func header_rename(string name, string newname; number idx) 21do 22 if not defined(idx) 23 set idx 1 24 fi 25 number msg current_message() 26 try 27 do 28 string value message_find_header(msg, name, idx) 29 header_delete(name, idx) 30 header_add(newname, value) 31 done 32 catch e_range 33 do 34 pass 35 done 36done 37 38/* 39 * Prefixes all headers with the given NAME with PREFIX. 40 * If PREFIX is not supplied, removes the matching headers. 41 */ 42func header_prefix_all(string name; string prefix) 43do 44 number msg current_message() 45 loop for number i message_header_count(msg, name), 46 while i >= 1, 47 set i i - 1 48 do 49 if defined(prefix) 50 header_rename(name, prefix . name, i) 51 else 52 header_delete(name, i) 53 fi 54 done 55done 56 57/* 58 * header_prefix_pattern(string pattern; string prefix) 59 * ---------------------------------------------------- 60 * Prefixes all headers that match PATTERN with PREFIX 61 * 62 * Implementing such functionality faces considerable difficulties because 63 * the milter API doesn't provide calls for iterating over all message 64 * headers. 65 * The basic algorithm is: 66 * 1. get the list of available messages from the captured message 67 * 2. for each header that matches pattern 68 * 2.1. save its value 69 * 2.2. delete that header 70 * 2.3. insert new header with the name constructed as concatenation 71 * or the prefix and original header name and value saved in 2.1 72 * 73 * The 2.3 should ideally preserve locations of the renamed headers. 74 * However, that is impossible using the Milter API. It was consented then 75 * that it should preserve relative ordering of the renamed headers. 76 * 77 * The initial approach was to use header_add function. Unfortunately, it 78 * follows whimsical rules when selecting the position where to insert the 79 * header. Normally, headers are added to the end of the list. However, if 80 * a header with such name already exists, the new one is added before it. 81 * The flags assigned to the header (H_USER and H_TRACE, in Sendmail) alter 82 * that decision. Consequently, using header_add produces unjustifiably 83 * complex code. 84 * 85 * Then, there is header_insert. Formally, it inserts the header at the given 86 * index in the header list. However, the header list it operates on is not 87 * the list of headers as present in the message, but an opaque internal list 88 * maintained in MTA, whose contents is unknown to the filter and is updated 89 * after the filter finishes its job. The only more or less reliable way of 90 * using this call is with index 0, which is supposed to add the header at 91 * the beginning of that internal list. At least, this ensures that the 92 * relative ordering of the renamed headers is preserved. 93 * Obviously the renamed headers must be inserted in the reverse order. 94 * 95 * So, the modified algorithm is: 96 * 1. Build a temporary list of headers that match the pattern. Each 97 * element of the list contains the header name and its instance number. 98 * The list is sorted in reverse order: the last header in the message 99 * appears at the start of the list. 100 * Here, the difficulty is the lack of dictionary data type in MFL, so 101 * the list is built as colon-delimited list of header specifications. 102 * Each specification is HDR/N, where HDR is the header name and N is 103 * its instance number. 104 * 2. Iterate over that list using the `string_list_iterate' macro. For 105 * each element in the list 106 * 2.1. save the header value 107 * 2.2. delete that header 108 * 2.3. insert the renamed header in position 0. 109 */ 110#pragma regex push +extended 111func header_prefix_pattern(string pattern; string prefix) 112do 113 number msg current_message() 114 number count message_header_count(msg) 115 string h_list ':' 116 117 loop for number i 1, 118 while i <= count, 119 set i i + 1 120 do 121 string name message_nth_header_name(msg, i) 122 if name fnmatches pattern 123 if h_list matches ".*:%name/([[:digit:]]+):.*" 124 set h_list ":%name/" . (\1 + 1) . h_list 125 else 126 set h_list ":%name/1" . h_list 127 fi 128 fi 129 done 130 131 if h_list matches '^:(.+):$' 132 set h_list \1 133 fi 134 135 string_list_iterate(h_list, ":", h_spec, ` 136 if h_spec matches "^(.+)/([[:digit:]]+)$" 137 string value message_find_header(msg, \1, \2) 138 header_delete(\1, \2) 139 if defined(prefix) 140 header_add(prefix . \1, value, 0) 141 fi 142 fi') 143done 144 145#pragma regex pop 146 147 148 149 150