* Implement a different way to delete a password from the cache.
[alpine.git] / pith / detach.c
blob6cb85e4d99a044ed8fcda6e6000a1dc6a6acc19a
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 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/detach.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/store.h"
20 #include "../pith/filter.h"
21 #include "../pith/mailview.h"
22 #include "../pith/status.h"
23 #include "../pith/addrstring.h"
24 #include "../pith/bldaddr.h"
25 #include "../pith/mimedesc.h"
26 #include "../pith/adjtime.h"
27 #include "../pith/pipe.h"
28 #include "../pith/busy.h"
29 #include "../pith/signal.h"
30 #include "../pico/osdep/filesys.h"
34 * We need to define simple functions here for the piping and
35 * temporary storage object below. We can use the filter.c functions
36 * because they're already in use for the "putchar" function passed to
37 * detach.
39 static STORE_S *detach_so = NULL;
43 * The display filter is locally global because it's set in df_trigger_cmp
44 * which sniffs at lines of the unencoded segment...
46 typedef struct _trigger {
47 int (*cmp)(char *, char *);
48 char *text;
49 char *cmd;
50 struct _trigger *next;
51 } TRGR_S;
53 static char *display_filter;
54 static TRGR_S *df_trigger_list;
56 FETCH_READC_S *g_fr_desc;
58 #define INIT_FETCH_CHUNK ((unsigned long)(8 * 1024L))
59 #define MIN_FETCH_CHUNK ((unsigned long)(4 * 1024L))
60 #define MAX_FETCH_CHUNK ((unsigned long)(256 * 1024L))
61 #define TARGET_INTR_TIME ((unsigned long)2000000L) /* two seconds */
62 #define FETCH_READC g_fr_desc->readc
66 * Internal Prototypes
68 /* HISTORICAL COMMENT
69 * This function is intentionally declared without an argument type so
70 * that warnings will go away in Windows. We're using gf_io_t for both
71 * input and output functions and the arguments aren't actually the
72 * same in the two cases. We should really have a read version and
73 * a write version of gf_io_t. That's why this is like this for now.
75 * EXPANDED VERSION
76 * The previous paragraph refers to the definition of the detach_writec
77 * function. It used to be that that function was defined as
78 * detach_writec();
79 * without any internal prototype. The reason why this was defined like
80 * that was because a function of type gf_io_t could be used to read
81 * data from or write data to a store object. In the case that a function
82 * of type gf_io_t is used to read data its internal prototype is of
83 * type (unsigned char *), and in the case that it is used to write
84 * data its internal prototype is of type (int).
86 * One way to deal with this is to make functions of type gf_io_t
87 * with internal prototype of type (void *) and to cast this to (int)
88 * and (unsigned char *) as needed, but then we get to into casting
89 * of different size warnings that will never lose information, but
90 * are not nice to read, so the solution is to really eliminate the
91 * type gf_io_t and split it into two types:
92 * One type is gf_i_t with internal prototype (unsigned char *) and
93 * another is gf_o_t with internal prototype (int).
95 * In the source code of Alpine the functions of this type are typically
96 * called gc for functions of the type gf_i_t, where gc stands for getchar,
97 * and pc for functions of the type gf_o_t, where pc stands for putchar.
98 * So a pc function writes data, and a gc function reads data. The gf_io_t
99 * type is gone. I have left some comments that refer to this type in the
100 * source code for historical reasons, now they must be interpreted as
101 * a gf_i_t or gf_o_t as appropiate, depending on if we need to read or
102 * write to a store object.
104 * One last thing, the gf_ prefix is for generic filter. Most of the gf_
105 * functions live in the file pith/filter.c, but there is a chance one can
106 * a function outside there. Have fun searching for them. ECh, 01/30/2024.
108 int detach_writec(int);
109 TRGR_S *build_trigger_list(void);
110 void blast_trigger_list(TRGR_S **);
111 int df_trigger_cmp(long, char *, LT_INS_S **, void *);
112 int df_trigger_cmp_text(char *, char *);
113 int df_trigger_cmp_lwsp(char *, char *);
114 int df_trigger_cmp_start(char *, char *);
115 int fetch_readc_cleanup(int);
116 char *fetch_gets(readfn_t, void *, unsigned long, GETS_DATA *);
117 int fetch_readc(unsigned char *);
121 /*----------------------------------------------------------------------
122 detach the given raw body part; don't do any decoding
124 Args: a bunch
126 Returns: NULL on success, error message otherwise
127 ----*/
128 char *
129 detach_raw(MAILSTREAM *stream, /* c-client stream to use */
130 long int msg_no, /* message number to deal with */
131 char *part_no, /* part number of message */
132 gf_o_t pc, /* where to put it */
133 int flags)
135 FETCH_READC_S *frd = (FETCH_READC_S *)fs_get(sizeof(FETCH_READC_S));
136 char *err = NULL;
137 int column = (flags & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;
139 memset(g_fr_desc = frd, 0, sizeof(FETCH_READC_S));
140 frd->stream = stream;
141 frd->msgno = msg_no;
142 frd->section = part_no;
143 frd->size = 0; /* wouldn't be here otherwise */
144 frd->readc = fetch_readc;
145 frd->chunk = pine_mail_fetch_text(stream, msg_no, NULL, &frd->read, 0);
146 frd->endp = &frd->chunk[frd->read];
147 frd->chunkp = frd->chunk;
149 gf_filter_init();
150 if (!(flags & FM_NOWRAP))
151 gf_link_filter(gf_wrap, gf_wrap_filter_opt(column, column, NULL, 0,
152 (flags & FM_DISPLAY)
153 ? GFW_HANDLES : 0));
154 err = gf_pipe(FETCH_READC, pc);
156 return(err);
160 /*----------------------------------------------------------------------
161 detach the given body part using the given encoding
163 Args: a bunch
165 Returns: NULL on success, error message otherwise
166 ----*/
167 char *
168 detach(MAILSTREAM *stream, /* c-client stream to use */
169 long int msg_no, /* message number to deal with */
170 char *part_no, /* part number of message */
171 long int partial, /* if >0, limit read to this many bytes */
172 long int *len, /* returns bytes read in this arg */
173 gf_o_t pc, /* where to put it */
174 FILTLIST_S *aux_filters, /* null terminated array of filts */
175 long flags)
177 unsigned long rv;
178 unsigned long size;
179 long fetch_flags;
180 int we_cancel = 0, is_text;
181 char *status, trigger[MAILTMPLEN];
182 char *charset = NULL;
183 BODY *body;
184 static char err_string[100];
185 FETCH_READC_S fetch_part;
187 err_string[0] = '\0';
189 if(!ps_global->print && !pc_is_picotext(pc))
190 we_cancel = busy_cue(NULL, NULL, 1);
192 gf_filter_init(); /* prepare for filtering! */
194 if(!(body = mail_body(stream, msg_no, (unsigned char *) part_no)))
195 return(_("Can't find body for requested message"));
197 is_text = body->type == TYPETEXT;
199 size = body->size.bytes;
200 if(partial > 0L && partial < size)
201 size = partial;
203 fetch_flags = (flags & ~DT_NODFILTER);
204 fetch_readc_init(&fetch_part, stream, msg_no, part_no, body->size.bytes, partial,
205 fetch_flags);
206 rv = size ? size : 1;
208 switch(body->encoding) { /* handle decoding */
209 case ENC7BIT:
210 case ENC8BIT:
211 case ENCBINARY:
212 break;
214 case ENCBASE64:
215 gf_link_filter(gf_b64_binary, NULL);
216 break;
218 case ENCQUOTEDPRINTABLE:
219 gf_link_filter(gf_qp_8bit, NULL);
220 break;
222 case ENCOTHER:
223 default:
224 dprint((1, "detach: unknown CTE: \"%s\" (%d)\n",
225 (body->encoding <= ENCMAX
226 && body_encodings[body->encoding])
227 ? body_encodings[body->encoding]
228 : "BEYOND-KNOWN-TYPES",
229 body->encoding));
230 break;
233 /* convert all text to UTF-8 */
234 if(is_text & !(flags & (DT_BINARY | DT_EXTERNAL))){
235 charset = parameter_val(body->parameter, "charset");
238 * If the charset is unlabeled or unknown replace it
239 * with the user's configured unknown charset.
241 if(!charset || !strucmp(charset, UNKNOWN_CHARSET)
242 || !strucmp(charset, "MISSING_PARAMETER_VALUE")
243 || !strucmp(charset, "us-ascii")){
244 if(charset)
245 fs_give((void **) &charset);
247 if(ps_global->VAR_UNK_CHAR_SET)
248 charset = cpystr(ps_global->VAR_UNK_CHAR_SET);
251 /* some messages are mislabeled as iso-8859-1 when in reality
252 * they are windows-1252, so we treat them as windows-1252
253 * from the beginning. If we did not make this change, we might
254 * see '?' characters in the message, without an apparent explanation
255 * of this fact.
257 if(charset && !strucmp(charset, "iso-8859-1")){
258 fs_give((void **) &charset);
259 charset = cpystr("windows-1252");
262 /* convert to UTF-8 */
263 if(!(charset && !strucmp(charset, "utf-8")))
264 gf_link_filter(gf_utf8, gf_utf8_opt(charset));
266 if(charset)
267 fs_give((void **) &charset);
271 * If we're detaching a text segment and there are user-defined
272 * filters and there are text triggers to look for, install filter
273 * to let us look at each line...
275 display_filter = NULL;
276 if(is_text
277 && ps_global->tools.display_filter
278 && ps_global->tools.display_filter_trigger
279 && ps_global->VAR_DISPLAY_FILTERS
280 && !(flags & DT_NODFILTER)){
281 /* check for "static" triggers (i.e., none or CHARSET) */
282 if(!(display_filter = (*ps_global->tools.display_filter_trigger)(body, trigger, sizeof(trigger)))
283 && (df_trigger_list = build_trigger_list())){
284 /* else look for matching text trigger */
285 gf_link_filter(gf_line_test,
286 gf_line_test_opt(df_trigger_cmp, NULL));
289 else
290 /* add aux filters if we're not going to MIME decode into a temporary
291 * storage object, otherwise we pass the aux_filters on to gf_filter
292 * below so it can pass what comes out of the external filter command
293 * thru the rest of the filters...
295 for( ; aux_filters && aux_filters->filter; aux_filters++)
296 gf_link_filter(aux_filters->filter, aux_filters->data);
299 * Following canonical model, after decoding convert newlines from
300 * crlf to local convention. ALSO, convert newlines if we're fetching
301 * a multipart segment since an external handler's going to have to
302 * make sense of it...
304 if((is_text & !(flags & DT_BINARY))
305 || body->type == TYPEMESSAGE
306 || body->type == TYPEMULTIPART)
307 gf_link_filter(gf_nvtnl_local, NULL);
309 if(flags & DT_EXTERNAL){
310 char i = flags & DT_ALLIMAGES ? 'i' : '\0';
311 gf_link_filter(gf_html_cid2file, (void *) &i);
315 * If we're detaching a text segment and a user-defined filter may
316 * need to be invoked later (see below), decode the segment into
317 * a temporary storage object...
319 if(is_text
320 && ps_global->tools.display_filter
321 && ps_global->tools.display_filter_trigger
322 && ps_global->VAR_DISPLAY_FILTERS
323 && !(flags & DT_NODFILTER)
324 && !(detach_so = so_get(CharStar, NULL, EDIT_ACCESS))){
325 strncpy(err_string,
326 _("Formatting error: no space to make copy, no display filters used"), sizeof(err_string));
327 err_string[sizeof(err_string)-1] = '\0';
330 if((status = gf_pipe(FETCH_READC, detach_so ? detach_writec : pc)) != NULL) {
331 snprintf(err_string, sizeof(err_string), "Formatting error: %s", status);
332 rv = 0L;
336 * If we wrote to a temporary area, there MAY be a user-defined
337 * filter to invoke. Filter it it (or not if no trigger match)
338 * *AND* send the output thru any auxiliary filters, destroy the
339 * temporary object and be done with it...
341 if(detach_so){
342 if(!err_string[0] && display_filter && *display_filter){
343 FILTLIST_S *p, *aux = NULL;
344 size_t count;
346 if(aux_filters && !(flags & DT_BINARY)){
347 /* insert NL conversion filters around remaining aux_filters
348 * so they're not tripped up by local NL convention
350 for(p = aux_filters; p->filter; p++) /* count aux_filters */
353 count = (p - aux_filters) + 3;
354 p = aux = (FILTLIST_S *) fs_get(count * sizeof(FILTLIST_S));
355 memset(p, 0, count * sizeof(FILTLIST_S));
356 p->filter = gf_local_nvtnl;
357 p++;
358 for(; aux_filters->filter; p++, aux_filters++)
359 *p = *aux_filters;
361 p->filter = gf_nvtnl_local;
364 if((status = (*ps_global->tools.display_filter)(display_filter, detach_so, pc, aux)) != NULL){
365 snprintf(err_string, sizeof(err_string), "Formatting error: %s", status);
366 rv = 0L;
369 if(aux)
370 fs_give((void **)&aux);
372 else{ /* just copy it, then */
373 gf_i_t gc;
375 gf_set_so_readc(&gc, detach_so);
376 so_seek(detach_so, 0L, 0);
377 gf_filter_init();
378 if(aux_filters){
379 /* if other filters are involved, correct for
380 * newlines on either side of the pipe...
382 gf_link_filter(gf_local_nvtnl, NULL);
383 for( ; aux_filters->filter ; aux_filters++)
384 gf_link_filter(aux_filters->filter, aux_filters->data);
386 if(!(flags & DT_BINARY))
387 gf_link_filter(gf_nvtnl_local, NULL);
390 if((status = gf_pipe(gc, pc)) != NULL){ /* Second pass, sheesh */
391 snprintf(err_string, sizeof(err_string), "Formatting error: %s", status);
392 rv = 0L;
395 gf_clear_so_readc(detach_so);
398 so_give(&detach_so); /* blast temp copy */
401 if(!ps_global->print && we_cancel)
402 cancel_busy_cue(0);
404 if (len)
405 *len = rv;
407 if(df_trigger_list)
408 blast_trigger_list(&df_trigger_list);
410 return((err_string[0] == '\0') ? NULL : err_string);
415 detach_writec(int c)
417 return(so_writec(c, detach_so));
422 * build_trigger_list - return possible triggers in a list of triggers
423 * structs
425 TRGR_S *
426 build_trigger_list(void)
428 TRGR_S *tp = NULL, **trailp;
429 char **l, *test, *str, *ep, *cmd = NULL;
430 int i;
432 trailp = &tp;
433 for(l = ps_global->VAR_DISPLAY_FILTERS ; l && *l; l++){
434 get_pair(*l, &test, &cmd, 1, 1);
435 if(test && valid_filter_command(&cmd)){
436 *trailp = (TRGR_S *) fs_get(sizeof(TRGR_S));
437 (*trailp)->cmp = df_trigger_cmp_text;
438 str = test;
439 if(*test == '_' && (i = strlen(test)) > 10
440 && *(ep = &test[i-1]) == '_' && *--ep == ')'){
441 if(struncmp(test, "_CHARSET(", 9) == 0){
442 fs_give((void **)&test);
443 fs_give((void **)&cmd);
444 fs_give((void **)trailp);
445 continue;
448 if(strncmp(test+1, "LEADING(", 8) == 0){
449 (*trailp)->cmp = df_trigger_cmp_lwsp;
450 *ep = '\0';
451 str = cpystr(test+9);
452 fs_give((void **)&test);
454 else if(strncmp(test+1, "BEGINNING(", 10) == 0){
455 (*trailp)->cmp = df_trigger_cmp_start;
456 *ep = '\0';
457 str = cpystr(test+11);
458 fs_give((void **)&test);
462 (*trailp)->text = str;
463 (*trailp)->cmd = cmd;
464 *(trailp = &(*trailp)->next) = NULL;
466 else{
467 fs_give((void **)&test);
468 fs_give((void **)&cmd);
472 return(tp);
477 * blast_trigger_list - zot any list of triggers we've been using
479 void
480 blast_trigger_list(TRGR_S **tlist)
482 if((*tlist)->next)
483 blast_trigger_list(&(*tlist)->next);
485 fs_give((void **)&(*tlist)->text);
486 fs_give((void **)&(*tlist)->cmd);
487 fs_give((void **)tlist);
492 * df_trigger_cmp - compare the line passed us with the list of defined
493 * display filter triggers
496 df_trigger_cmp(long int n, char *s, LT_INS_S **e, void *l)
498 register TRGR_S *tp;
499 int result;
501 if(!display_filter) /* already found? */
502 for(tp = df_trigger_list; tp; tp = tp->next)
503 if(tp->cmp){
504 if((result = (*tp->cmp)(s, tp->text)) < 0)
505 tp->cmp = NULL;
506 else if(result > 0)
507 return(((display_filter = tp->cmd) != NULL) ? 1 : 0);
510 return(0);
515 * df_trigger_cmp_text - return 1 if s1 is in s2
518 df_trigger_cmp_text(char *s1, char *s2)
520 return(strstr(s1, s2) != NULL);
525 * df_trigger_cmp_lwsp - compare the line passed us with the list of defined
526 * display filter triggers. returns:
528 * 0 if we don't know yet
529 * 1 if we match
530 * -1 if we clearly don't match
533 df_trigger_cmp_lwsp(char *s1, char *s2)
535 while(*s1 && isspace((unsigned char)*s1))
536 s1++;
538 return((*s1) ? (!strncmp(s1, s2, strlen(s2)) ? 1 : -1) : 0);
543 * df_trigger_cmp_start - return 1 if first strlen(s2) chars start s1
546 df_trigger_cmp_start(char *s1, char *s2)
548 return(!strncmp(s1, s2, strlen(s2)));
553 * valid_filter_command - make sure argv[0] of command really exists.
554 * "cmd" is required to be an alloc'd string since
555 * it will get realloc'd if the command's path is
556 * expanded.
559 valid_filter_command(char **cmd)
561 int i;
562 char cpath[MAXPATH+1], *p;
564 if(!(cmd && *cmd))
565 return(FALSE);
568 * copy cmd to build expanded path if necessary.
570 for(i = 0; i < sizeof(cpath) && (cpath[i] = (*cmd)[i]); i++)
571 if(isspace((unsigned char)(*cmd)[i])){
572 cpath[i] = '\0'; /* tie off command's path*/
573 break;
576 #if defined(DOS) || defined(OS2)
577 if(is_absolute_path(cpath)){
578 size_t l;
580 fixpath(cpath, sizeof(cpath));
581 l = strlen(cpath) + strlen(&(*cmd)[i]);
582 p = (char *) fs_get((l+1) * sizeof(char));
583 strncpy(p, cpath, l); /* copy new path */
584 p[l] = '\0';
585 strncat(p, &(*cmd)[i], l+1-1-strlen(p)); /* and old args */
586 p[l] = '\0';
587 fs_give((void **) cmd); /* free it */
588 *cmd = p; /* and assign new buf */
590 #else
591 if(cpath[0] == '~'){
592 if(fnexpand(cpath, sizeof(cpath))){
593 size_t l;
595 l = strlen(cpath) + strlen(&(*cmd)[i]);
596 p = (char *) fs_get((l+1) * sizeof(char));
597 strncpy(p, cpath, l); /* copy new path */
598 p[l] = '\0';
599 strncat(p, &(*cmd)[i], l+1-1-strlen(p)); /* and old args */
600 p[l] = '\0';
601 fs_give((void **) cmd); /* free it */
602 *cmd = p; /* and assign new buf */
604 else
605 return(FALSE);
607 #endif
609 return(is_absolute_path(cpath) && can_access(cpath, EXECUTE_ACCESS) == 0);
613 void
614 fetch_readc_init(FETCH_READC_S *frd, MAILSTREAM *stream, long int msgno,
615 char *section, unsigned long size, long partial, long int flags)
617 int nointr = 0;
619 nointr = flags & DT_NOINTR;
620 flags &= ~DT_NOINTR;
622 memset(g_fr_desc = frd, 0, sizeof(FETCH_READC_S));
623 frd->stream = stream;
624 frd->msgno = msgno;
625 frd->section = section;
626 frd->flags = flags;
627 frd->size = size;
628 frd->readc = fetch_readc;
630 #ifdef SMIME
632 * The call to imap_cache below will return true in the case where
633 * we've already stashed fake data in the content of the part.
634 * This happens when an S/MIME message is decrypted.
636 #endif
638 if(modern_imap_stream(stream)
639 && !imap_cache(stream, msgno, section, NULL, NULL)
640 && (size > INIT_FETCH_CHUNK || (partial > 0L && partial < size))
641 && (F_OFF(F_QUELL_PARTIAL_FETCH, ps_global)
643 #ifdef _WINDOWS
644 F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)
645 #else
647 #endif
650 if(partial > 0L && partial < size){
651 /* partial fetch is being asked for */
652 frd->size = partial;
655 frd->allocsize = MIN(INIT_FETCH_CHUNK,frd->size);
656 frd->chunk = (char *) fs_get ((frd->allocsize + 1) * sizeof(char));
657 frd->chunksize = frd->allocsize/2; /* this gets doubled 1st time */
658 frd->endp = frd->chunk;
659 frd->free_me = 1;
661 if(!nointr)
662 if(intr_handling_on())
663 frd->we_turned_on = 1;
665 if(!(partial > 0L && partial < size)){
666 frd->cache = so_get(CharStar, NULL, EDIT_ACCESS);
667 so_truncate(frd->cache, size); /* pre-allocate */
670 else{ /* fetch the whole bloody thing here */
671 frd->chunk = mail_fetch_body(stream, msgno, section, &frd->read, flags);
673 /* This only happens if the server gave us a bogus size */
674 if(partial > 0L && partial < size){
675 /* partial fetch is being asked for */
676 frd->size = partial;
677 frd->endp = &frd->chunk[frd->size];
679 else if(size != frd->read){
680 dprint((1,
681 "fetch_readc_init: size mismatch: size=%lu read=%lu, continue...\n",
682 frd->size, frd->read));
683 q_status_message(SM_ORDER | SM_DING, 0, 3,
684 _("Message size does not match expected size, continuing..."));
685 frd->size = MIN(size, frd->read);
686 frd->endp = &frd->chunk[frd->read];
688 else
689 frd->endp = &frd->chunk[frd->read];
692 frd->chunkp = frd->chunk;
697 fetch_readc_cleanup(int store)
699 if(g_fr_desc){
700 if(g_fr_desc->we_turned_on)
701 intr_handling_off();
703 if(g_fr_desc->chunk && g_fr_desc->free_me)
704 fs_give((void **) &g_fr_desc->chunk);
706 if(g_fr_desc->cache && store){
707 SIZEDTEXT text;
709 text.size = g_fr_desc->size;
710 text.data = (unsigned char *) so_text(g_fr_desc->cache);
711 imap_cache(g_fr_desc->stream, g_fr_desc->msgno,
712 g_fr_desc->section, NULL, &text);
713 g_fr_desc->cache->txt = (void *) NULL;
714 so_give(&g_fr_desc->cache);
718 return(0);
722 char *
723 fetch_gets(readfn_t f, void *stream, long unsigned int size, GETS_DATA *md)
725 unsigned long n;
727 n = MIN(g_fr_desc->chunksize, size);
728 g_fr_desc->read += n;
729 g_fr_desc->endp = &g_fr_desc->chunk[n];
731 (*f) (stream, n, g_fr_desc->chunkp = g_fr_desc->chunk);
733 if(g_fr_desc->cache)
734 so_nputs(g_fr_desc->cache, g_fr_desc->chunk, (long) n);
736 /* BUG: need to read requested "size" in case it's larger than chunk? */
738 return(NULL);
743 fetch_readc(unsigned char *c)
745 extern void gf_error(char *);
747 if(ps_global->intr_pending){
748 (void) fetch_readc_cleanup(0);
749 /* TRANSLATORS: data transfer was interrupted by something */
750 gf_error(g_fr_desc->error ? g_fr_desc->error :_("Transfer interrupted!"));
751 /* no return */
753 else if(g_fr_desc->chunkp == g_fr_desc->endp){
755 /* Anything to read, do it */
756 if(g_fr_desc->read < g_fr_desc->size){
757 void *old_gets;
758 int rv;
759 TIMEVAL_S before, after;
760 long diff, wdiff;
761 unsigned long save_read;
763 old_gets = mail_parameters(g_fr_desc->stream, GET_GETS,
764 (void *)NULL);
765 mail_parameters(g_fr_desc->stream, SET_GETS, (void *) fetch_gets);
768 * Adjust chunksize with the goal that it will be about
769 * TARGET_INTR_TIME useconds +- 20%
770 * to finish the partial fetch. We want that time
771 * to be small so that interrupts will happen fast, but we want
772 * the chunksize large so that the whole fetch will happen
773 * fast. So it's a tradeoff between those two things.
775 * If the estimated fetchtime is getting too large, we
776 * half the chunksize. If it is small, we double
777 * the chunksize. If it is in between, we leave it. There is
778 * some risk of oscillating between two values, but who cares?
780 if(g_fr_desc->fetchtime <
781 TARGET_INTR_TIME - TARGET_INTR_TIME/5)
782 g_fr_desc->chunksize *= 2;
783 else if(g_fr_desc->fetchtime >
784 TARGET_INTR_TIME + TARGET_INTR_TIME/5)
785 g_fr_desc->chunksize /= 2;
787 g_fr_desc->chunksize = MIN(MAX_FETCH_CHUNK,
788 MAX(MIN_FETCH_CHUNK,
789 g_fr_desc->chunksize));
791 #ifdef _WINDOWS
793 * If this feature is set, limit the max size to less than
794 * 16K - 5, the magic number that avoids Microsoft's bug.
795 * Let's just go with 12K instead of 16K - 5.
797 if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global))
798 g_fr_desc->chunksize =
799 MIN(AVOID_MICROSOFT_SSL_CHUNKING_BUG, g_fr_desc->chunksize);
800 #endif
802 /* don't ask for more than there should be left to ask for */
803 g_fr_desc->chunksize =
804 MIN(g_fr_desc->size - g_fr_desc->read, g_fr_desc->chunksize);
807 * If chunksize grew, reallocate chunk.
809 if(g_fr_desc->chunksize > g_fr_desc->allocsize){
810 g_fr_desc->allocsize = g_fr_desc->chunksize;
811 fs_give((void **) &g_fr_desc->chunk);
812 g_fr_desc->chunk = (char *) fs_get ((g_fr_desc->allocsize + 1)
813 * sizeof(char));
814 g_fr_desc->endp = g_fr_desc->chunk;
815 g_fr_desc->chunkp = g_fr_desc->chunk;
818 save_read = g_fr_desc->read;
819 (void)get_time(&before);
821 rv = mail_partial_body(g_fr_desc->stream, g_fr_desc->msgno,
822 g_fr_desc->section, g_fr_desc->read,
823 g_fr_desc->chunksize, g_fr_desc->flags);
826 * If the amount we actually read is less than the amount we
827 * asked for we assume that is because the server gave us a
828 * bogus size when we originally asked for it.
830 if(g_fr_desc->chunksize > (g_fr_desc->read - save_read)){
831 dprint((1,
832 "partial_body returned less than asked for: asked=%lu got=%lu, continue...\n",
833 g_fr_desc->chunksize, g_fr_desc->read - save_read));
834 if(g_fr_desc->read - save_read > 0)
835 q_status_message(SM_ORDER | SM_DING, 0, 3,
836 _("Message size does not match expected size, continuing..."));
837 else{
838 rv = 0;
839 q_status_message(SM_ORDER | SM_DING, 3, 3,
840 _("Server returns zero bytes, Quell-Partial-Fetch feature may help"));
843 g_fr_desc->size = g_fr_desc->read;
846 if(get_time(&after) == 0){
847 diff = time_diff(&after, &before);
848 wdiff = MIN(TARGET_INTR_TIME + TARGET_INTR_TIME/2,
849 MAX(TARGET_INTR_TIME - TARGET_INTR_TIME/2, diff));
851 * Fetchtime is an exponentially weighted average of the number
852 * of usecs it takes to do a single fetch of whatever the
853 * current chunksize is. Since the fetch time probably isn't
854 * simply proportional to the chunksize, we don't try to
855 * calculate a chunksize by keeping track of the bytes per
856 * second. Instead, we just double or half the chunksize if
857 * we are too fast or too slow. That happens the next time
858 * through the loop a few lines up.
859 * Too settle it down a bit, Windsorize the mean.
861 g_fr_desc->fetchtime = (g_fr_desc->fetchtime == 0)
862 ? wdiff
863 : g_fr_desc->fetchtime/2 + wdiff/2;
864 dprint((8,
865 "fetch: diff=%ld wdiff=%ld fetchave=%ld prev chunksize=%ld\n",
866 diff, wdiff, g_fr_desc->fetchtime, g_fr_desc->chunksize));
868 else /* just set it so it won't affect anything */
869 g_fr_desc->fetchtime = TARGET_INTR_TIME;
871 /* UNinstall mailgets */
872 mail_parameters(g_fr_desc->stream, SET_GETS, old_gets);
874 if(!rv){
875 (void) fetch_readc_cleanup(0);
876 gf_error("Partial fetch failed!");
877 /* no return */
880 else /* clean up and return done. */
881 return(fetch_readc_cleanup(1));
884 *c = *g_fr_desc->chunkp++;
886 return(1);