1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils 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    GNU Mailutils 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 GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "mail.h"
18 #include <mailutils/locker.h>
19 
20 /*
21  * c[opy] [file]
22  * c[opy] [msglist] file
23  * C[opy] [msglist]
24  */
25 
26 struct append_stat
27 {
28   size_t size;
29   size_t lines;
30 };
31 
32 static int
append_to_mailbox(mu_url_t url,msgset_t * msglist,int mark,struct append_stat * totals)33 append_to_mailbox (mu_url_t url, msgset_t *msglist, int mark,
34 		   struct append_stat *totals)
35 {
36   int status;
37   mu_mailbox_t mbx;
38   msgset_t *mp;
39   size_t size;
40   mu_message_t msg;
41   mu_url_t url_copy;
42 
43   mu_url_dup (url, &url_copy);
44   if ((status = mu_mailbox_create_from_url (&mbx, url_copy)) != 0)
45     {
46       mu_url_destroy (&url_copy);
47       mu_error (_("Cannot create mailbox %s: %s"), mu_url_to_string (url),
48 		   mu_strerror (status));
49       return 1;
50     }
51   mu_mailbox_attach_ticket (mbx);
52   if ((status = mu_mailbox_open (mbx, MU_STREAM_CREAT | MU_STREAM_APPEND)) != 0)
53     {
54       mu_error (_("Cannot open mailbox %s: %s"), mu_url_to_string (url),
55 		   mu_strerror (status));
56       mu_mailbox_destroy (&mbx);
57       return 1;
58     }
59 
60   for (mp = msglist; mp; mp = mp->next)
61     {
62       status = util_get_message (mbox, msgset_msgno (mp), &msg);
63       if (status)
64 	break;
65 
66       status = mu_mailbox_append_message (mbx, msg);
67       if (status)
68 	{
69 	  mu_error (_("Cannot append message: %s"), mu_strerror (status));
70 	  break;
71 	}
72 
73       mu_message_size (msg, &size);
74       totals->size += size;
75       mu_message_lines (msg, &size);
76       totals->lines += size;
77 
78       if (mark)
79 	{
80 	  mu_attribute_t attr;
81 	  mu_message_get_attribute (msg, &attr);
82 	  mu_attribute_set_userflag (attr, MAIL_ATTRIBUTE_SAVED);
83 	}
84     }
85 
86   mu_mailbox_close (mbx);
87   mu_mailbox_destroy (&mbx);
88   return 0;
89 }
90 
91 static int
append_to_file(char const * filename,msgset_t * msglist,int mark,struct append_stat * totals)92 append_to_file (char const *filename, msgset_t *msglist, int mark,
93 		struct append_stat *totals)
94 {
95   int status;
96   msgset_t *mp;
97   mu_stream_t ostr, mstr;
98   mu_message_t msg;
99   mu_locker_t locker;
100   mu_locker_hints_t hints = {
101     .flags = MU_LOCKER_FLAG_TYPE | MU_LOCKER_FLAG_RETRY,
102     .type = MU_LOCKER_TYPE_KERNEL
103   };
104   mu_stream_stat_buffer stat;
105 
106   status = mu_file_stream_create (&ostr, filename,
107 				  MU_STREAM_CREAT|MU_STREAM_APPEND);
108   if (status)
109     {
110       mu_error (_("Cannot open output file %s: %s"),
111 		filename, mu_strerror (status));
112       return 1;
113     }
114 
115   status = mu_locker_create_ext (&locker, filename, &hints);
116   if (status)
117     {
118       mu_error (_("Cannot create locker %s: %s"),
119 		filename, mu_strerror (status));
120       mu_stream_unref (ostr);
121       return 1;
122     }
123   mu_locker_lock_mode (locker, mu_lck_exc);
124 
125   status = mu_locker_lock (locker);
126   if (status)
127     {
128       mu_error (_("Cannot lock %s: %s"),
129 		filename, mu_strerror (status));
130       mu_locker_destroy (&locker);
131       mu_stream_unref (ostr);
132       return 1;
133     }
134 
135   mu_stream_set_stat (ostr,
136 		      MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT) |
137 		      MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUTLN),
138 		      stat);
139 
140   for (mp = msglist; mp; mp = mp->next)
141     {
142       mu_envelope_t env;
143       const char *s, *d;
144       int n;
145 
146       status = util_get_message (mbox, msgset_msgno (mp), &msg);
147       if (status)
148 	break;
149 
150       status = mu_message_get_envelope (msg, &env);
151       if (status)
152 	{
153 	  mu_error (_("Cannot get envelope: %s"), mu_strerror (status));
154 	  break;
155 	}
156 
157       status = mu_envelope_sget_sender (env, &s);
158       if (status)
159 	{
160 	  mu_error (_("Cannot get envelope sender: %s"), mu_strerror (status));
161 	  break;
162 	}
163 
164       status = mu_envelope_sget_date (env, &d);
165       if (status)
166 	{
167 	  mu_error (_("Cannot get envelope date: %s"), mu_strerror (status));
168 	  break;
169 	}
170 
171       status = mu_stream_printf (ostr, "From %s %s\n%n", s, d, &n);
172       if (status)
173 	{
174 	  mu_error (_("Write error: %s"), mu_strerror (status));
175 	  break;
176 	}
177 
178       status = mu_message_get_streamref (msg, &mstr);
179       if (status)
180 	{
181 	  mu_error (_("Cannot get message: %s"), mu_strerror (status));
182 	  break;
183 	}
184 
185       status = mu_stream_copy_nl (ostr, mstr, 0, NULL);
186       if (status)
187 	{
188 	  mu_error (_("Cannot append message: %s"), mu_strerror (status));
189 	  break;
190 	}
191 
192       mu_stream_unref (mstr);
193 
194       if (mark)
195 	{
196 	  mu_attribute_t attr;
197 	  mu_message_get_attribute (msg, &attr);
198 	  mu_attribute_set_userflag (attr, MAIL_ATTRIBUTE_SAVED);
199 	}
200     }
201 
202   mu_stream_close (ostr);
203   mu_stream_unref (ostr);
204 
205   mu_locker_unlock (locker);
206   mu_locker_destroy (&locker);
207 
208   totals->size = stat[MU_STREAM_STAT_OUT];
209   totals->lines = stat[MU_STREAM_STAT_OUTLN];
210 
211   return 0;
212 }
213 
214 /*
215  * mail_copy0() is shared between mail_copy() and mail_save().
216  * argc, argv -- argument count & vector
217  * mark -- whether we should mark the message as saved.
218  */
219 int
mail_copy0(int argc,char ** argv,int mark)220 mail_copy0 (int argc, char **argv, int mark)
221 {
222   mu_url_t url;
223   msgset_t *msglist = NULL;
224   struct append_stat totals = { 0, 0 };
225   int rc;
226   char *filename, *storage = NULL;
227 
228   if (mu_isupper (argv[0][0]))
229     {
230       if (msgset_parse (argc, argv, MSG_NODELETED, &msglist))
231 	return 1;
232       storage = util_get_sender (msgset_msgno (msglist), 1);
233       filename = util_outfolder_name (storage);
234       if (filename)
235 	{
236 	  free (storage);
237 	  storage = filename;
238 	}
239       else
240 	filename = storage;
241     }
242   else
243     {
244       filename = argc >= 2 ? argv[--argc] : getenv ("MBOX");
245       if (msgset_parse (argc, argv, MSG_NODELETED, &msglist))
246 	return 1;
247     }
248 
249   rc = mail_expand_name (filename, &url);
250   if (rc == 0)
251     {
252       filename = (char*) mu_url_to_string (url);
253       if (mu_url_is_scheme (url, "file") || mu_url_is_scheme (url, "mbox"))
254 	rc = append_to_file (filename, msglist, mark, &totals);
255       else
256 	rc = append_to_mailbox (url, msglist, mark, &totals);
257       if (rc == 0)
258 	mu_printf ("\"%s\" %3lu/%-5lu\n", filename,
259 		   (unsigned long) totals.lines, (unsigned long) totals.size);
260       mu_url_destroy (&url);
261     }
262   free (storage);
263   msgset_free (msglist);
264   return rc;
265 }
266 
267 int
mail_copy(int argc,char ** argv)268 mail_copy (int argc, char **argv)
269 {
270   return mail_copy0 (argc, argv, 0);
271 }
272