Add support for tab-completion when selecting by rule
[alpine.git] / pith / news.c
blobd92ec383ef23d93bbec72b291db1e358a14c009c
1 /*
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/news.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/context.h"
20 #include "../pith/stream.h"
21 #include "../pith/util.h"
24 typedef enum {NotChecked, NotInCache, Found, Missing, End} NgCacheReturns;
28 * Internal prototypes
30 NgCacheReturns chk_newsgrp_cache(char *);
31 void add_newsgrp_cache(char *, NgCacheReturns);
34 /*----------------------------------------------------------------------
35 Function to see if a given MAILSTREAM mailbox is in the news namespace
37 Input: stream -- mail stream to test
39 Result:
40 ----*/
41 int
42 ns_test(char *mailbox, char *namespace)
44 if(mailbox){
45 switch(*mailbox){
46 case '#' :
47 return(!struncmp(mailbox + 1, namespace, strlen(namespace)));
49 case '{' :
51 NETMBX mbx;
53 if(mail_valid_net_parse(mailbox, &mbx))
54 return(ns_test(mbx.mailbox, namespace));
57 break;
59 default :
60 break;
64 return(0);
68 int
69 news_in_folders(struct variable *var)
71 int i, found_news = 0;
72 CONTEXT_S *tc;
74 if(!(var && var->current_val.l))
75 return(found_news);
77 for(i=0; !found_news && var->current_val.l[i]; i++){
78 if((tc = new_context(var->current_val.l[i], NULL)) != NULL){
79 if(tc->use & CNTXT_NEWS)
80 found_news++;
82 free_context(&tc);
86 return(found_news);
90 /*----------------------------------------------------------------------
91 Verify and canonicalize news groups names.
92 Called from the message composer
94 Args: given_group -- List of groups typed by user
95 expanded_group -- pointer to point to expanded list, which will be
96 allocated here and freed in caller. If this is
97 NULL, don't attempt to validate.
98 error -- pointer to store error message
99 fcc -- pointer to point to fcc, which will be
100 allocated here and freed in caller
102 Returns: 0 if all is OK
103 -1 if addresses weren't valid
105 Test the given list of newstroups against those recognized by our nntp
106 servers. Testing by actually trying to open the list is much cheaper, both
107 in bandwidth and memory, than yanking the whole list across the wire.
108 ----*/
110 news_grouper(char *given_group, char **expanded_group, char **error,
111 char **fccptr, void (*delay_warning)(void))
113 char ng_error[90], *p1, *p2, *name, *end, *ep, **server,
114 ng_ref[MAILTMPLEN];
115 int expanded_len = 0, num_in_error = 0, cnt_errs = 0;
117 MAILSTREAM *stream = NULL;
118 struct ng_list {
119 char *groupname;
120 NgCacheReturns found;
121 struct ng_list *next;
122 }*nglist = NULL, **ntmpp, *ntmp;
123 #ifdef SENDNEWS
124 static int no_servers = 0;
125 #endif
127 dprint((5,
128 "- news_build - (%s)\n", given_group ? given_group : "nul"));
130 if(error)
131 *error = NULL;
133 ng_ref[0] = '\0';
135 /*------ parse given entries into a list ----*/
136 ntmpp = &nglist;
137 for(name = given_group; *name; name = end){
139 /* find start of next group name */
140 while(*name && (isspace((unsigned char)*name) || *name == ','))
141 name++;
143 /* find end of group name */
144 end = name;
145 while(*end && !isspace((unsigned char)*end) && *end != ',')
146 end++;
148 if(end != name){
149 *ntmpp = (struct ng_list *)fs_get(sizeof(struct ng_list));
150 (*ntmpp)->next = NULL;
151 (*ntmpp)->found = NotChecked;
152 (*ntmpp)->groupname = fs_get(end - name + 1);
153 strncpy((*ntmpp)->groupname, name, end - name);
154 (*ntmpp)->groupname[end - name] = '\0';
155 ntmpp = &(*ntmpp)->next;
156 if(!expanded_group)
157 break; /* no need to continue if just doing fcc */
162 * If fcc is not set or is set to default, then replace it if
163 * one of the recipient rules is in effect.
165 if(fccptr){
166 if((ps_global->fcc_rule == FCC_RULE_RECIP ||
167 ps_global->fcc_rule == FCC_RULE_NICK_RECIP) &&
168 (nglist && nglist->groupname)){
169 if(*fccptr)
170 fs_give((void **) fccptr);
172 *fccptr = cpystr(nglist->groupname);
174 else if(!*fccptr) /* already default otherwise */
175 *fccptr = cpystr(ps_global->VAR_DEFAULT_FCC);
178 if(!nglist){
179 if(expanded_group)
180 *expanded_group = cpystr("");
181 return 0;
184 if(!expanded_group)
185 return 0;
187 #ifdef DEBUG
188 for(ntmp = nglist; debug >= 9 && ntmp; ntmp = ntmp->next)
189 dprint((9, "Parsed group: --[%s]--\n",
190 ntmp->groupname ? ntmp->groupname : "?"));
191 #endif
193 /* If we are doing validation */
194 if(F_OFF(F_NO_NEWS_VALIDATION, ps_global)){
195 int need_to_talk_to_server = 0;
198 * First check our cache of validated newsgroups to see if we even
199 * have to open a stream.
201 for(ntmp = nglist; ntmp; ntmp = ntmp->next){
202 ntmp->found = chk_newsgrp_cache(ntmp->groupname);
203 if(ntmp->found == NotInCache)
204 need_to_talk_to_server++;
207 if(need_to_talk_to_server){
209 #ifdef SENDNEWS
210 if(no_servers == 0)
211 #endif
212 if(delay_warning)
213 (*delay_warning)();
216 * Build a stream to the first server that'll talk to us...
218 for(server = ps_global->VAR_NNTP_SERVER;
219 server && *server && **server;
220 server++){ /* MAILTMPLEN = sizeof(ng_ref) */
221 snprintf(ng_ref, sizeof(ng_ref), "{%.*s/nntp}#news.",
222 MAILTMPLEN-30, *server);
223 if((stream = pine_mail_open(stream, ng_ref,
224 OP_HALFOPEN|SP_USEPOOL|SP_TEMPUSE,
225 NULL)) != NULL)
226 break;
228 if(!server || !stream){
229 if(error)
230 #ifdef SENDNEWS
232 /* don't say this over and over */
233 if(no_servers == 0){
234 if(!server || !*server || !**server)
235 no_servers++;
237 *error = cpystr(no_servers
238 /* TRANSLATORS: groups refers to news groups */
239 ? _("Can't validate groups. No servers defined")
240 /* TRANSLATORS: groups refers to news groups */
241 : _("Can't validate groups. No servers responding"));
244 #else
245 *error = cpystr((!server || !*server || !**server)
246 ? _("No servers defined for posting to newsgroups")
247 /* TRANSLATORS: groups refers to news groups */
248 : _("Can't validate groups. No servers responding"));
249 #endif
250 *expanded_group = cpystr(given_group);
251 goto done;
256 * Now, go thru the list, making sure we can at least open each one...
258 for(server = ps_global->VAR_NNTP_SERVER;
259 server && *server && **server; server++){
261 * It's faster and easier right now just to open the stream and
262 * do our own finds than to use the current folder_exists()
263 * interface...
265 for(ntmp = nglist; ntmp; ntmp = ntmp->next){
266 if(ntmp->found == NotInCache){ /* MAILTMPLEN = sizeof(ng_ref) */
267 snprintf(ng_ref, sizeof(ng_ref), "{%.*s/nntp}#news.%.*s",
268 MAILTMPLEN/2 - 10, *server,
269 MAILTMPLEN/2 - 10, ntmp->groupname);
270 ps_global->noshow_error = 1;
271 stream = pine_mail_open(stream, ng_ref,
272 OP_SILENT|SP_USEPOOL|SP_TEMPUSE,
273 NULL);
274 ps_global->noshow_error = 0;
275 if(stream)
276 add_newsgrp_cache(ntmp->groupname, ntmp->found = Found);
281 if(stream){
282 pine_mail_close(stream);
283 stream = NULL;
290 /* figure length of string for matching groups */
291 for(ntmp = nglist; ntmp; ntmp = ntmp->next){
292 if(ntmp->found == Found || F_ON(F_NO_NEWS_VALIDATION, ps_global))
293 expanded_len += strlen(ntmp->groupname) + 2;
294 else{
295 num_in_error++;
296 if(ntmp->found == NotInCache)
297 add_newsgrp_cache(ntmp->groupname, ntmp->found = Missing);
302 * allocate and write the allowed, and error lists...
304 p1 = *expanded_group = fs_get((expanded_len + 1) * sizeof(char));
305 if(error && num_in_error){
306 cnt_errs = num_in_error;
307 memset((void *)ng_error, 0, sizeof(ng_error));
308 snprintf(ng_error, sizeof(ng_error), "Unknown news group%s: ", plural(num_in_error));
309 ep = ng_error + strlen(ng_error);
311 for(ntmp = nglist; ntmp; ntmp = ntmp->next){
312 p2 = ntmp->groupname;
313 if(ntmp->found == Found || F_ON(F_NO_NEWS_VALIDATION, ps_global)){
314 while(*p2)
315 *p1++ = *p2++;
317 if(ntmp->next){
318 *p1++ = ',';
319 *p1++ = ' ';
322 else if (error){
323 while(*p2 && (ep - ng_error < sizeof(ng_error)-1))
324 *ep++ = *p2++;
326 if(--cnt_errs > 0 && (ep - ng_error < sizeof(ng_error)-3)){
327 strncpy(ep, ", ", sizeof(ng_error)-(ep-ng_error));
328 ep += 2;
333 *p1 = '\0';
335 if(error && num_in_error)
336 *error = cpystr(ng_error);
338 done:
339 while((ntmp = nglist) != NULL){
340 nglist = nglist->next;
341 fs_give((void **)&ntmp->groupname);
342 fs_give((void **)&ntmp);
345 return(num_in_error ? -1 : 0);
349 typedef struct ng_cache {
350 char *name;
351 NgCacheReturns val;
352 }NgCache;
354 static NgCache *ng_cache_ptr;
355 #if defined(DOS) && !defined(_WINDOWS)
356 #define MAX_NGCACHE_ENTRIES 15
357 #else
358 #define MAX_NGCACHE_ENTRIES 40
359 #endif
361 * Simple newsgroup validity cache. Opening a newsgroup to see if it
362 * exists can be very slow on a heavily loaded NNTP server, so we cache
363 * the results.
365 NgCacheReturns
366 chk_newsgrp_cache(char *group)
368 register NgCache *ngp;
370 for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++){
371 if(strcmp(group, ngp->name) == 0)
372 return(ngp->val);
375 return NotInCache;
380 * Add an entry to the newsgroup validity cache.
382 * LRU entry is the one on the bottom, oldest on the top.
383 * A slot has an entry in it if name is not NULL.
385 void
386 add_newsgrp_cache(char *group, NgCacheReturns result)
388 register NgCache *ngp;
389 NgCache save_ngp;
391 /* first call, initialize cache */
392 if(!ng_cache_ptr){
393 int i;
395 ng_cache_ptr =
396 (NgCache *)fs_get((MAX_NGCACHE_ENTRIES+1)*sizeof(NgCache));
397 for(i = 0; i <= MAX_NGCACHE_ENTRIES; i++){
398 ng_cache_ptr[i].name = NULL;
399 ng_cache_ptr[i].val = NotInCache;
401 ng_cache_ptr[MAX_NGCACHE_ENTRIES].val = End;
404 if(chk_newsgrp_cache(group) == NotInCache){
405 /* find first empty slot or End */
406 for(ngp = ng_cache_ptr; ngp->name; ngp++)
407 ;/* do nothing */
408 if(ngp->val == End){
410 * Cache is full, throw away top entry, move everything up,
411 * and put new entry on the bottom.
413 ngp = ng_cache_ptr;
414 if(ngp->name) /* just making sure */
415 fs_give((void **)&ngp->name);
417 for(; (ngp+1)->name; ngp++){
418 ngp->name = (ngp+1)->name;
419 ngp->val = (ngp+1)->val;
422 ngp->name = cpystr(group);
423 ngp->val = result;
425 else{
427 * Move this entry from current location to last to preserve
428 * LRU order.
430 for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++){
431 if(strcmp(group, ngp->name) == 0) /* found it */
432 break;
434 save_ngp.name = ngp->name;
435 save_ngp.val = ngp->val;
436 for(; (ngp+1)->name; ngp++){
437 ngp->name = (ngp+1)->name;
438 ngp->val = (ngp+1)->val;
440 ngp->name = save_ngp.name;
441 ngp->val = save_ngp.val;
446 void
447 free_newsgrp_cache(void)
449 register NgCache *ngp;
451 for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++)
452 fs_give((void **)&ngp->name);
453 if(ng_cache_ptr)
454 fs_give((void **)&ng_cache_ptr);