2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2007 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/sequence.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/msgno.h"
20 #include "../pith/flag.h"
21 #include "../pith/thread.h"
22 #include "../pith/icache.h"
30 /*----------------------------------------------------------------------
31 Build comma delimited list of selected messages
33 Args: stream -- mail stream to use for flag testing
34 msgmap -- message number struct of to build selected messages in
35 count -- pointer to place to write number of comma delimited
36 mark -- mark manually undeleted so we don't refilter right away
38 Returns: malloc'd string containing sequence, else NULL if
39 no messages in msgmap with local "selected" flag.
42 selected_sequence(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int *count
, int mark
)
51 * The plan here is to use the c-client elt's "sequence" bit
52 * to work around any orderings or exclusions in pine's internal
53 * mapping that might cause the sequence to be artificially
54 * lengthy. It's probably cheaper to run down the elt list
55 * twice rather than call nm_raw2m() for each message as
56 * we run down the elt list once...
58 for(i
= 1L; i
<= stream
->nmsgs
; i
++)
59 if((mc
= mail_elt(stream
, i
)) != NULL
)
62 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
63 if(get_lflag(stream
, msgmap
, i
, MN_SLCT
)){
68 * Forget we knew about it, and set "add to sequence"
71 clear_index_cache_ent(stream
, i
, 0);
72 rawno
= mn_m2raw(msgmap
, i
);
73 if(rawno
> 0L && rawno
<= stream
->nmsgs
74 && (mc
= mail_elt(stream
, rawno
)))
78 * Mark this message manually flagged so we don't re-filter it
79 * with a filter which only sets flags.
82 if(msgno_exceptions(stream
, rawno
, "0", &exbits
, FALSE
))
83 exbits
|= MSG_EX_MANUNDEL
;
85 exbits
= MSG_EX_MANUNDEL
;
87 msgno_exceptions(stream
, rawno
, "0", &exbits
, TRUE
);
91 return(build_sequence(stream
, NULL
, count
));
95 /*----------------------------------------------------------------------
96 Build comma delimited list of messages current in msgmap which have all
97 flags matching the arguments
99 Args: stream -- mail stream to use for flag testing
100 msgmap -- only consider messages selected in this msgmap
101 flag -- flags to match against
102 count -- pointer to place to return number of comma delimited
103 mark -- mark index cache entry changed, and count state change
104 kw_on -- if flag contains F_KEYWORD, this is
105 the array of keywords to be checked
106 kw_off -- if flag contains F_UNKEYWORD, this is
107 the array of keywords to be checked
109 Returns: malloc'd string containing sequence, else NULL if
110 no messages in msgmap with local "selected" flag (a flag
111 of zero means all current msgs).
114 currentf_sequence(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int flag
,
115 long int *count
, int mark
, char **kw_on
, char **kw_off
)
125 /* First, make sure elts are valid for all the interesting messages */
126 if((seq
= invalid_elt_sequence(stream
, msgmap
)) != NULL
){
127 pine_mail_fetch_flags(stream
, seq
, NIL
);
128 fs_give((void **) &seq
);
131 for(i
= 1L; i
<= stream
->nmsgs
; i
++)
132 if((mc
= mail_elt(stream
, i
)) != NULL
)
133 mc
->sequence
= 0; /* clear "sequence" bits */
135 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
136 /* if not already set, go on... */
137 rawno
= mn_m2raw(msgmap
, i
);
138 mc
= (rawno
> 0L && rawno
<= stream
->nmsgs
)
139 ? mail_elt(stream
, rawno
) : NULL
;
144 || ((flag
& F_DEL
) && mc
->deleted
)
145 || ((flag
& F_UNDEL
) && !mc
->deleted
)
146 || ((flag
& F_SEEN
) && mc
->seen
)
147 || ((flag
& F_UNSEEN
) && !mc
->seen
)
148 || ((flag
& F_ANS
) && mc
->answered
)
149 || ((flag
& F_UNANS
) && !mc
->answered
)
150 || ((flag
& F_FLAG
) && mc
->flagged
)
151 || ((flag
& F_UNFLAG
) && !mc
->flagged
)){
152 mc
->sequence
= 1; /* set "sequence" flag */
155 /* check for forwarded status */
158 && user_flag_is_set(stream
, rawno
, FORWARDED_FLAG
)){
164 && !user_flag_is_set(stream
, rawno
, FORWARDED_FLAG
)){
168 /* check for user keywords or not */
169 if(!mc
->sequence
&& flag
& F_KEYWORD
&& kw_on
){
170 for(t
= kw_on
; !mc
->sequence
&& *t
; t
++)
171 if(user_flag_is_set(stream
, rawno
, *t
))
174 else if(!mc
->sequence
&& flag
& F_UNKEYWORD
&& kw_off
){
175 for(t
= kw_off
; !mc
->sequence
&& *t
; t
++)
176 if(!user_flag_is_set(stream
, rawno
, *t
))
186 /* clear thread index line instead of index index line */
187 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, i
));
189 && (thrd
=fetch_thread(stream
,thrd
->top
))
190 && (t
= mn_raw2m(msgmap
, thrd
->rawno
)))
191 clear_index_cache_ent(stream
, t
, 0);
194 clear_index_cache_ent(stream
, i
, 0);/* force new index line */
197 * Mark this message manually flagged so we don't re-filter it
198 * with a filter which only sets flags.
201 if(msgno_exceptions(stream
, rawno
, "0", &exbits
, FALSE
))
202 exbits
|= MSG_EX_MANUNDEL
;
204 exbits
= MSG_EX_MANUNDEL
;
206 msgno_exceptions(stream
, rawno
, "0", &exbits
, TRUE
);
211 return(build_sequence(stream
, NULL
, count
));
215 /*----------------------------------------------------------------------
216 Return sequence numbers of messages with invalid MESSAGECACHEs
218 Args: stream -- mail stream to use for flag testing
219 msgmap -- message number struct of to build selected messages in
221 Returns: malloc'd string containing sequence, else NULL if
222 no messages in msgmap with local "selected" flag (a flag
223 of zero means all current msgs).
226 invalid_elt_sequence(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
234 for(i
= 1L; i
<= stream
->nmsgs
; i
++)
235 if((mc
= mail_elt(stream
, i
)) != NULL
)
236 mc
->sequence
= 0; /* clear "sequence" bits */
238 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
))
239 if((rawno
= mn_m2raw(msgmap
, i
)) > 0L && rawno
<= stream
->nmsgs
240 && (mc
= mail_elt(stream
, rawno
)) && !mc
->valid
)
243 return(build_sequence(stream
, NULL
, NULL
));
247 /*----------------------------------------------------------------------
248 Build comma delimited list of messages with elt "sequence" bit set
250 Args: stream -- mail stream to use for flag testing
251 msgmap -- struct containing sort to build sequence in
252 count -- pointer to place to write number of comma delimited
253 NOTE: if non-zero, it's a clue as to how many messages
254 have the sequence bit lit.
256 Returns: malloc'd string containing sequence, else NULL if
257 no messages in msgmap with elt's "sequence" bit set
260 build_sequence(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int *count
)
262 #define SEQ_INCREMENT 128
263 long n
= 0L, i
, x
, lastn
= 0L, runstart
= 0L;
264 size_t size
= SEQ_INCREMENT
;
265 char *seq
= NULL
, *p
;
273 size
= MAX(size
, MIN((*count
) * 4, 16384));
278 for(x
= 1L; x
<= stream
->nmsgs
; x
++){
280 if((i
= mn_m2raw(msgmap
, x
)) == 0L)
286 if(i
> 0L && i
<= stream
->nmsgs
287 && (mc
= mail_elt(stream
, i
)) && mc
->sequence
){
289 if(!seq
) /* initialize if needed */
290 seq
= p
= fs_get(size
);
293 * This code will coalesce the ascending runs of
294 * sequence numbers, but fails to break sequences
295 * into a reasonably sensible length for imapd's to
296 * swallow (reasonable addtition to c-client?)...
298 if(lastn
){ /* if may be in a run */
299 if(lastn
+ 1L == i
){ /* and its the next raw num */
300 lastn
= i
; /* skip writing anything... */
303 else if(runstart
!= lastn
){
304 *p
++ = (runstart
+ 1L == lastn
) ? ',' : ':';
305 sstrncpy(&p
, long2string(lastn
), size
-(p
-seq
));
306 } /* wrote end of run */
309 runstart
= lastn
= i
; /* remember last raw num */
311 if(n
> 1L && (p
-seq
) < size
) /* !first num, write delim */
314 if(size
- (p
- seq
) < 16){ /* room for two more nums? */
315 size_t offset
= p
- seq
; /* grow the sequence array */
316 size
+= SEQ_INCREMENT
;
317 fs_resize((void **)&seq
, size
);
321 sstrncpy(&p
, long2string(i
), size
-(p
-seq
)); /* write raw number */
325 if(lastn
&& runstart
!= lastn
){ /* were in a run? */
326 if((p
-seq
) < size
) /* !first num, write delim */
327 *p
++ = (runstart
+ 1L == lastn
) ? ',' : ':';
329 sstrncpy(&p
, long2string(lastn
), size
-(p
-seq
)); /* write the trailing num */
332 if(seq
&& (p
-seq
) < size
) /* if sequence, tie it off */
345 /*----------------------------------------------------------------------
346 If any messages flagged "selected", fake the "currently selected" array
348 Args: map -- message number struct of to build selected messages in
350 OK folks, here's the tradeoff: either all the functions have to
351 know if the user wants to deal with the "current" hilited message
352 or the list of currently "selected" messages, *or* we just
353 wrap the call to these functions with some glue that tweeks
354 what these functions see as the "current" message list, and let them
358 pseudo_selected(MAILSTREAM
*stream
, MSGNO_S
*map
)
362 if(any_lflagged(map
, MN_SLCT
)){
363 map
->hilited
= mn_m2raw(map
, mn_get_cur(map
));
365 for(i
= 1L; i
<= mn_get_total(map
); i
++)
366 if(get_lflag(stream
, map
, i
, MN_SLCT
)){
382 /*----------------------------------------------------------------------
383 Antidote for the monkey business committed above
385 Args: map -- message number struct of to build selected messages in
389 restore_selected(MSGNO_S
*map
)
392 mn_reset_cur(map
, mn_raw2m(map
, map
->hilited
));
399 * Return a search set which can be used to limit the search to a smaller set,
400 * for performance reasons. The caller must still work correctly if we return
401 * the whole set (or NULL) here, because we may not be able to send the full
402 * search set over IMAP. In cases where the search set description is getting
403 * too large we send a search set which contains all of the relevant messages.
404 * It may contain more.
407 * narrow -- If set, we are narrowing our selection (restrict to
408 * messages with MN_SLCT already set) or if not set,
409 * we are broadening (so we may look only at messages
410 * with MN_SLCT not set)
412 * Returns - allocated search set or NULL. Caller is responsible for freeing it.
415 limiting_searchset(MAILSTREAM
*stream
, int narrow
)
419 SEARCHSET
*full_set
= NULL
, **set
;
422 * If we're talking to anything other than a server older than
423 * imap 4rev1, build a searchset otherwise it'll choke.
425 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
426 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
427 /* test for end of run */
428 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)
429 || (narrow
&& !get_lflag(stream
, NULL
, n
, MN_SLCT
))
430 || (!narrow
&& get_lflag(stream
, NULL
, n
, MN_SLCT
))){
431 if(run
){ /* previous selected? */
436 else if(run
++){ /* next in run */
439 else{ /* start of run */
440 *set
= mail_newsearchset();
441 (*set
)->first
= (*set
)->last
= n
;
444 * Make this last set cover the rest of the messages.
445 * We could be fancier about this but it probably isn't
449 (*set
)->last
= stream
->nmsgs
;