Fix [1785be65] from 2017-03-xx
[s-mailx.git] / cmd-write.c
blob4330a0f1f18ba0dfc4b1223acd4689b68530095d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ User commands which save, copy, write (parts of) messages.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd_write
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Save/copy the indicated messages at the end of the passed file name.
43 * If mark is true, mark the message "saved" */
44 static int save1(char *str, int domark, char const *cmd,
45 struct n_ignore const *itp, int convert, int sender_record,
46 int domove);
48 /* Snarf the file from the end of the command line and return a pointer to it.
49 * If there is no file attached, return the mbox file. Put a null in front of
50 * the file name so that the message list processing won't see it, unless the
51 * file name is the only thing on the line, in which case, return 0 in the
52 * reference flag variable */
53 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
55 static int
56 save1(char *str, int domark, char const *cmd, struct n_ignore const *itp,
57 int convert, int sender_record, int domove)
59 ui64_t mstats[1], tstats[2];
60 enum n_fopen_state fs;
61 int last, *msgvec, *ip;
62 struct message *mp;
63 char *file, *cp, *cq;
64 char const *disp, *shell;
65 FILE *obuf;
66 bool_t success, isflag;
67 NYD_ENTER;
69 success = FAL0;
70 last = 0;
71 shell = NULL;
72 file = NULL;
74 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
75 if (sender_record) {
76 for (cp = str; *cp != '\0' && spacechar(*cp); ++cp)
78 isflag = (*cp != '\0');
79 } else {
80 if ((file = snarf(str, &isflag, convert != SEND_TOFILE)) == NULL)
81 goto jleave;
82 while(spacechar(*file))
83 ++file;
84 if (*file == '|') {
85 ++file;
86 shell = ok_vlook(SHELL);
90 if (!isflag) {
91 *msgvec = first(0, MMNORM);
92 msgvec[1] = 0;
93 } else if (getmsglist(str, msgvec, 0) < 0)
94 goto jleave;
95 if (*msgvec == 0) {
96 if (n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) {
97 success = TRU1;
98 goto jleave;
100 fprintf(n_stdout, _("No messages to %s.\n"), cmd);
101 goto jleave;
104 if (sender_record) {
105 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
106 fprintf(n_stdout, _("Cannot determine message sender to %s.\n"), cmd);
107 goto jleave;
110 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
112 *cq = '\0';
113 if (ok_blook(outfolder)) {
114 size_t sz = strlen(cp) +1;
115 file = salloc(sz + 1);
116 file[0] = '+';
117 memcpy(file + 1, cp, sz);
118 } else
119 file = cp;
122 /* Pipe target is special TODO hacked in later, normalize flow! */
123 if (shell != NULL) {
124 if ((obuf = Popen(file, "w", shell, NULL, 1)) == NULL) {
125 int esave;
127 esave = n_err_no;
128 n_perr(file, esave);
129 n_err_no = esave;
130 goto jleave;
132 isflag = FAL0;
133 disp = A_("[Piped]");
134 goto jsend;
137 if ((file = fexpand(file, FEXP_FULL)) == NULL)
138 goto jleave;
140 /* TODO all this should be URL and Mailbox-"VFS" based, and then finally
141 * TODO end up as Mailbox()->append(). Unless SEND_TOFILE, of course.
142 * TODO However, URL parse because that file:// prefix check is a HACK! */
143 if(convert == SEND_TOFILE && !is_prefix("file://", file))
144 file = savecat("file://", file);
145 if((obuf = n_fopen_any(file, "a+", &fs)) == NULL){
146 n_perr(file, 0);
147 goto jleave;
150 #if defined HAVE_POP3 && defined HAVE_IMAP
151 if(mb.mb_type == MB_POP3 && (fs & n_PROTO_MASK) == n_PROTO_IMAP){
152 Fclose(obuf);
153 n_err(_("Direct copy from POP3 to IMAP not supported before v15\n"));
154 goto jleave;
156 #endif
158 disp = (fs & n_FOPEN_STATE_EXISTS) ? A_("[Appended]") : A_("[New file]");
160 if((fs & (n_PROTO_MASK | n_FOPEN_STATE_EXISTS)) ==
161 (n_PROTO_FILE | n_FOPEN_STATE_EXISTS)){
162 int xerr;
164 /* TODO RETURN check, but be aware of protocols: v15: Mailbox->lock()!
165 * TODO BETTER yet: should be returned in lock state already! */
166 n_file_lock(fileno(obuf), FLT_WRITE, 0,0, UIZ_MAX);
168 if((xerr = n_folder_mbox_prepare_append(obuf, NULL)) != n_ERR_NONE){
169 n_perr(file, xerr);
170 goto jleave;
174 jsend:
175 success = TRU1;
176 tstats[0] = tstats[1] = 0;
177 #ifdef HAVE_IMAP
178 imap_created_mailbox = 0;
179 #endif
181 srelax_hold();
182 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
183 ++ip) {
184 mp = &message[*ip - 1];
185 #ifdef HAVE_IMAP
186 if((fs & n_PROTO_MASK) == n_PROTO_IMAP &&
187 !n_ignore_is_any(n_IGNORE_SAVE) && imap_thisaccount(file)){
188 if(imap_copy(mp, PTR2SIZE(mp - message + 1), file) == STOP){
189 success = FAL0;
190 goto jferr;
192 mstats[0] = mp->m_xsize;
193 }else
194 #endif
195 if (sendmp(mp, obuf, itp, NULL, convert, mstats) < 0) {
196 success = FAL0;
197 goto jferr;
199 srelax();
201 touch(mp);
202 if (domark)
203 mp->m_flag |= MSAVED;
204 if (domove) {
205 mp->m_flag |= MDELETED | MSAVED;
206 last = *ip;
209 tstats[0] += mstats[0];
210 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
212 srelax_rele();
214 fflush(obuf);
216 /* TODO Should be a VFS, then n_MBOX knows what to do upon .close()! */
217 if((fs & n_PROTO_MASK) == n_PROTO_FILE && convert == SEND_MBOX)
218 n_folder_mbox_prepare_append(obuf, NULL);
220 if (ferror(obuf)) {
221 jferr:
222 n_perr(file, 0);
223 if (!success)
224 srelax_rele();
225 success = FAL0;
227 if (shell != NULL) {
228 if (!Pclose(obuf, TRU1))
229 success = FAL0;
230 } else if (Fclose(obuf) != 0)
231 success = FAL0;
233 if (success) {
234 #ifdef HAVE_IMAP
235 if((fs & n_PROTO_MASK) == n_PROTO_IMAP){
236 if(disconnected(file))
237 disp = A_("[Queued]");
238 else if(imap_created_mailbox)
239 disp = A_("[New file]");
240 else
241 disp = A_("[Appended]");
243 #endif
244 fprintf(n_stdout, "%s %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
245 n_shexp_quote_cp(file, FAL0), disp,
246 /*tstats[1], TODO v15: lines written */ tstats[0]);
247 } else if (domark) {
248 for (ip = msgvec; *ip != 0 &&
249 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
250 mp = message + *ip - 1;
251 mp->m_flag &= ~MSAVED;
253 } else if (domove) {
254 for (ip = msgvec; *ip != 0 &&
255 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
256 mp = message + *ip - 1;
257 mp->m_flag &= ~(MSAVED | MDELETED);
261 if (domove && last && success) {
262 setdot(message + last - 1);
263 last = first(0, MDELETED);
264 setdot(message + (last != 0 ? last - 1 : 0));
266 jleave:
267 NYD_LEAVE;
268 return (success == FAL0);
271 static char *
272 snarf(char *linebuf, bool_t *flag, bool_t usembox)
274 char *cp;
275 NYD_ENTER;
277 if ((cp = laststring(linebuf, flag, TRU1)) == NULL) {
278 if (usembox) {
279 *flag = FAL0;
280 cp = fexpand("&", FEXP_NVAR);
281 } else
282 n_err(_("No file specified\n"));
284 NYD_LEAVE;
285 return cp;
288 FL int
289 c_save(void *v)
291 char *str = v;
292 int rv;
293 NYD_ENTER;
295 rv = save1(str, 1, "save", n_IGNORE_SAVE, SEND_MBOX, 0, 0);
296 NYD_LEAVE;
297 return rv;
300 FL int
301 c_Save(void *v)
303 char *str = v;
304 int rv;
305 NYD_ENTER;
307 rv = save1(str, 1, "save", n_IGNORE_SAVE, SEND_MBOX, 1, 0);
308 NYD_LEAVE;
309 return rv;
312 FL int
313 c_copy(void *v)
315 char *str = v;
316 int rv;
317 NYD_ENTER;
319 rv = save1(str, 0, "copy", n_IGNORE_SAVE, SEND_MBOX, 0, 0);
320 NYD_LEAVE;
321 return rv;
324 FL int
325 c_Copy(void *v)
327 char *str = v;
328 int rv;
329 NYD_ENTER;
331 rv = save1(str, 0, "copy", n_IGNORE_SAVE, SEND_MBOX, 1, 0);
332 NYD_LEAVE;
333 return rv;
336 FL int
337 c_move(void *v)
339 char *str = v;
340 int rv;
341 NYD_ENTER;
343 rv = save1(str, 0, "move", n_IGNORE_SAVE, SEND_MBOX, 0, 1);
344 NYD_LEAVE;
345 return rv;
348 FL int
349 c_Move(void *v)
351 char *str = v;
352 int rv;
353 NYD_ENTER;
355 rv = save1(str, 0, "move", n_IGNORE_SAVE, SEND_MBOX, 1, 1);
356 NYD_LEAVE;
357 return rv;
360 FL int
361 c_decrypt(void *v)
363 char *str = v;
364 int rv;
365 NYD_ENTER;
367 rv = save1(str, 0, "decrypt", n_IGNORE_SAVE, SEND_DECRYPT, 0, 0);
368 NYD_LEAVE;
369 return rv;
372 FL int
373 c_Decrypt(void *v)
375 char *str = v;
376 int rv;
377 NYD_ENTER;
379 rv = save1(str, 0, "decrypt", n_IGNORE_SAVE, SEND_DECRYPT, 1, 0);
380 NYD_LEAVE;
381 return rv;
384 FL int
385 c_write(void *v)
387 char *str = v;
388 int rv;
389 NYD_ENTER;
391 if (str == NULL || *str == '\0')
392 str = savestr(n_path_devnull);
393 rv = save1(str, 0, "write", n_IGNORE_ALL, SEND_TOFILE, 0, 0);
394 NYD_LEAVE;
395 return rv;
398 /* s-it-mode */