diff --git a/network/protocol/nntp/mknews.cpp b/network/protocol/nntp/mknews.cpp new file mode 100644 index 00000000000..f57454f7c0d --- /dev/null +++ b/network/protocol/nntp/mknews.cpp @@ -0,0 +1,5323 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "NPL"); you may not use this file except in + * compliance with the NPL. You may obtain a copy of the NPL at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the NPL is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL + * for the specific language governing rights and limitations under the + * NPL. + * + * The Initial Developer of this code under the NPL is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All Rights + * Reserved. + */ + +/* Please leave outside of ifdef for windows precompiled headers */ +#define FORCE_PR_LOG /* Allow logging in the release build (sorry this breaks the PCH) */ +#include "mkutils.h" +#include "netutils.h" + + +#ifdef MOZILLA_CLIENT + +#include "merrors.h" + +#include "mime.h" +#include "shist.h" +#include "glhist.h" +#include "xp_reg.h" +#include "mknews.h" +#include "mktcp.h" +#include "mkparse.h" +#include "mkformat.h" +#include "mkstream.h" +#include "mkaccess.h" +#include "mksort.h" +#include "netcache.h" +#if 0 +#include "mkextcac.h" +#endif +#include "libi18n.h" +#include "gui.h" + +#include "rosetta.h" +#include HG40855 +#include "mknews.h" + +#include "msgcom.h" +#include "msgnet.h" +#include "msg_srch.h" +#include "libmime.h" + +#include "prtime.h" +#include "prlog.h" +#include "prerror.h" + +#include "xp_error.h" +#include HG09893 +#include "prefapi.h" +#include "xplocale.h" + +/*#define CACHE_NEWSGRP_PASSWORD*/ + +/* for XP_GetString() */ +#include "xpgetstr.h" +extern int MK_MALFORMED_URL_ERROR; +extern int MK_NEWS_ERROR_FMT; +extern int MK_NNTP_CANCEL_CONFIRM; +extern int MK_NNTP_CANCEL_DISALLOWED; +extern int MK_NNTP_NOT_CANCELLED; +extern int MK_OUT_OF_MEMORY; +extern int XP_CONFIRM_SAVE_NEWSGROUPS; +extern int XP_HTML_ARTICLE_EXPIRED; +extern int XP_HTML_NEWS_ERROR; +extern int XP_PROGRESS_READ_NEWSGROUPINFO; +extern int XP_PROGRESS_RECEIVE_ARTICLE; +extern int XP_PROGRESS_RECEIVE_LISTARTICLES; +extern int XP_PROGRESS_RECEIVE_NEWSGROUP; +extern int XP_PROGRESS_SORT_ARTICLES; +extern int XP_PROGRESS_READ_NEWSGROUP_COUNTS; +extern int XP_THERMO_PERCENT_FORM; +extern int XP_PROMPT_ENTER_USERNAME; +extern int MK_BAD_NNTP_CONNECTION; +extern int MK_NNTP_AUTH_FAILED; +extern int MK_NNTP_ERROR_MESSAGE; +extern int MK_NNTP_NEWSGROUP_SCAN_ERROR; +extern int MK_NNTP_SERVER_ERROR; +extern int MK_NNTP_SERVER_NOT_CONFIGURED; +HG25431 +extern int MK_TCP_READ_ERROR; +extern int MK_TCP_WRITE_ERROR; +extern int MK_NNTP_CANCEL_ERROR; +extern int XP_CONNECT_NEWS_HOST_CONTACTED_WAITING_FOR_REPLY; +extern int XP_PLEASE_ENTER_A_PASSWORD_FOR_NEWS_SERVER_ACCESS; +extern int XP_GARBAGE_COLLECTING; +extern int XP_MESSAGE_SENT_WAITING_NEWS_REPLY; +extern int MK_MSG_DELIV_NEWS; +extern int MK_MSG_COLLABRA_DISABLED; +extern int MK_MSG_EXPIRE_NEWS_ARTICLES; +extern int MK_MSG_HTML_IMAP_NO_CACHED_BODY; + +/* Logging stuff */ + +PRLogModuleInfo* NNTP = NULL; +#define out PR_LOG_ALWAYS + +#define NNTP_LOG_READ(buf) \ +if (NNTP==NULL) \ + NNTP = PR_NewLogModule("NNTP"); \ +PR_LOG(NNTP, out, ("Receiving: %s", buf)) ; + +#define NNTP_LOG_WRITE(buf) \ +if (NNTP==NULL) \ + NNTP = PR_NewLogModule("NNTP"); \ +PR_LOG(NNTP, out, ("Sending: %s", buf)) ; + +#define NNTP_LOG_NOTE(buf) \ +if (NNTP==NULL) \ + NNTP = PR_NewLogModule("NNTP"); \ +PR_LOG(NNTP, out, buf) ; + + +/* end logging */ + +/* Forward declarations */ +static int net_xpat_send (ActiveEntry*); +static int net_list_pretty_names(ActiveEntry*); + +#ifdef PROFILE +#pragma profile on +#endif + +#define LIST_WANTED 0 +#define ARTICLE_WANTED 1 +#define CANCEL_WANTED 2 +#define GROUP_WANTED 3 +#define NEWS_POST 4 +#define READ_NEWS_RC 5 +#define NEW_GROUPS 6 +#define SEARCH_WANTED 7 +#define PRETTY_NAMES_WANTED 8 +#define PROFILE_WANTED 9 +#define IDS_WANTED 10 + +/* the output_buffer_size must be larger than the largest possible line + * 2000 seems good for news + * + * jwz: I increased this to 4k since it must be big enough to hold the + * entire button-bar HTML, and with the new "mailto" format, that can + * contain arbitrarily long header fields like "references". + * + * fortezza: proxy auth is huge, buffer increased to 8k (sigh). + */ +#define OUTPUT_BUFFER_SIZE (4096*2) + +/* the amount of time to subtract from the machine time + * for the newgroup command sent to the nntp server + */ +#define NEWGROUPS_TIME_OFFSET 60L*60L*12L /* 12 hours */ + +/* Allow the user to open at most this many connections to one news host*/ +#define kMaxConnectionsPerHost 3 + +/* Keep this many connections cached. The cache is global, not per host */ +#define kMaxCachedConnections 2 + +/* globals + */ +PRIVATE char * NET_NewsHost = NULL; +PRIVATE XP_List * nntp_connection_list=0; + +PRIVATE XP_Bool net_news_last_username_probably_valid=FALSE; +PRIVATE int32 net_NewsChunkSize=-1; /* default */ + +PRIVATE int32 net_news_timeout = 170; /* seconds that an idle NNTP conn can live */ + +#define PUTSTRING(s) (*cd->stream->put_block) \ + (cd->stream, s, PL_strlen(s)) +#define COMPLETE_STREAM (*cd->stream->complete) \ + (cd->stream) +#define ABORT_STREAM(s) (*cd->stream->abort) \ + (cd->stream, s) +#define PUT_STREAM(buf, size) (*cd->stream->put_block) \ + (cd->stream, buf, size) + +/* states of the machine + */ +typedef enum _StatesEnum { +NNTP_RESPONSE, +#ifdef BLOCK_UNTIL_AVAILABLE_CONNECTION +NNTP_BLOCK_UNTIL_CONNECTIONS_ARE_AVAILABLE, +NNTP_CONNECTIONS_ARE_AVAILABLE, +#endif +NNTP_CONNECT, +NNTP_CONNECT_WAIT, +HG07711 +NNTP_LOGIN_RESPONSE, +NNTP_SEND_MODE_READER, +NNTP_SEND_MODE_READER_RESPONSE, +SEND_LIST_EXTENSIONS, +SEND_LIST_EXTENSIONS_RESPONSE, +SEND_LIST_SEARCHES, +SEND_LIST_SEARCHES_RESPONSE, +NNTP_LIST_SEARCH_HEADERS, +NNTP_LIST_SEARCH_HEADERS_RESPONSE, +NNTP_GET_PROPERTIES, +NNTP_GET_PROPERTIES_RESPONSE, +SEND_LIST_SUBSCRIPTIONS, +SEND_LIST_SUBSCRIPTIONS_RESPONSE, +SEND_FIRST_NNTP_COMMAND, +SEND_FIRST_NNTP_COMMAND_RESPONSE, +SETUP_NEWS_STREAM, +NNTP_BEGIN_AUTHORIZE, +NNTP_AUTHORIZE_RESPONSE, +NNTP_PASSWORD_RESPONSE, +NNTP_READ_LIST_BEGIN, +NNTP_READ_LIST, +DISPLAY_NEWSGROUPS, +NNTP_NEWGROUPS_BEGIN, +NNTP_NEWGROUPS, +NNTP_BEGIN_ARTICLE, +NNTP_READ_ARTICLE, +NNTP_XOVER_BEGIN, +NNTP_FIGURE_NEXT_CHUNK, +NNTP_XOVER_SEND, +NNTP_XOVER_RESPONSE, +NNTP_XOVER, +NEWS_PROCESS_XOVER, +NNTP_READ_GROUP, +NNTP_READ_GROUP_RESPONSE, +NNTP_READ_GROUP_BODY, +NNTP_SEND_GROUP_FOR_ARTICLE, +NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE, +NNTP_PROFILE_ADD, +NNTP_PROFILE_ADD_RESPONSE, +NNTP_PROFILE_DELETE, +NNTP_PROFILE_DELETE_RESPONSE, +NNTP_SEND_ARTICLE_NUMBER, +NEWS_PROCESS_BODIES, +NNTP_PRINT_ARTICLE_HEADERS, +NNTP_SEND_POST_DATA, +NNTP_SEND_POST_DATA_RESPONSE, +NNTP_CHECK_FOR_MESSAGE, +NEWS_NEWS_RC_POST, +NEWS_DISPLAY_NEWS_RC, +NEWS_DISPLAY_NEWS_RC_RESPONSE, +NEWS_START_CANCEL, +NEWS_DO_CANCEL, +NNTP_XPAT_SEND, +NNTP_XPAT_RESPONSE, +NNTP_SEARCH, +NNTP_SEARCH_RESPONSE, +NNTP_SEARCH_RESULTS, +NNTP_LIST_PRETTY_NAMES, +NNTP_LIST_PRETTY_NAMES_RESPONSE, +NNTP_LIST_XACTIVE, +NNTP_LIST_XACTIVE_RESPONSE, +NNTP_LIST_GROUP, +NNTP_LIST_GROUP_RESPONSE, +NEWS_DONE, +NEWS_ERROR, +NNTP_ERROR, +NEWS_FREE +} StatesEnum; + +#ifdef DEBUG +char *stateLabels[] = { +"NNTP_RESPONSE", +#ifdef BLOCK_UNTIL_AVAILABLE_CONNECTION +"NNTP_BLOCK_UNTIL_CONNECTIONS_ARE_AVAILABLE", +"NNTP_CONNECTIONS_ARE_AVAILABLE", +#endif +"NNTP_CONNECT", +"NNTP_CONNECT_WAIT", +HG25430 +"NNTP_LOGIN_RESPONSE", +"NNTP_SEND_MODE_READER", +"NNTP_SEND_MODE_READER_RESPONSE", +"SEND_LIST_EXTENSIONS", +"SEND_LIST_EXTENSIONS_RESPONSE", +"SEND_LIST_SEARCHES", +"SEND_LIST_SEARCHES_RESPONSE", +"NNTP_LIST_SEARCH_HEADERS", +"NNTP_LIST_SEARCH_HEADERS_RESPONSE", +"NNTP_GET_PROPERTIES", +"NNTP_GET_PROPERTIES_RESPONSE", +"SEND_LIST_SUBSCRIPTIONS", +"SEND_LIST_SUBSCRIPTIONS_RESPONSE", +"SEND_FIRST_NNTP_COMMAND", +"SEND_FIRST_NNTP_COMMAND_RESPONSE", +"SETUP_NEWS_STREAM", +"NNTP_BEGIN_AUTHORIZE", +"NNTP_AUTHORIZE_RESPONSE", +"NNTP_PASSWORD_RESPONSE", +"NNTP_READ_LIST_BEGIN", +"NNTP_READ_LIST", +"DISPLAY_NEWSGROUPS", +"NNTP_NEWGROUPS_BEGIN", +"NNTP_NEWGROUPS", +"NNTP_BEGIN_ARTICLE", +"NNTP_READ_ARTICLE", +"NNTP_XOVER_BEGIN", +"NNTP_FIGURE_NEXT_CHUNK", +"NNTP_XOVER_SEND", +"NNTP_XOVER_RESPONSE", +"NNTP_XOVER", +"NEWS_PROCESS_XOVER", +"NNTP_READ_GROUP", +"NNTP_READ_GROUP_RESPONSE", +"NNTP_READ_GROUP_BODY", +"NNTP_SEND_GROUP_FOR_ARTICLE", +"NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE", +"NNTP_PROFILE_ADD", +"NNTP_PROFILE_ADD_RESPONSE", +"NNTP_PROFILE_DELETE", +"NNTP_PROFILE_DELETE_RESPONSE", +"NNTP_SEND_ARTICLE_NUMBER", +"NEWS_PROCESS_BODIES", +"NNTP_PRINT_ARTICLE_HEADERS", +"NNTP_SEND_POST_DATA", +"NNTP_SEND_POST_DATA_RESPONSE", +"NNTP_CHECK_FOR_MESSAGE", +"NEWS_NEWS_RC_POST", +"NEWS_DISPLAY_NEWS_RC", +"NEWS_DISPLAY_NEWS_RC_RESPONSE", +"NEWS_START_CANCEL", +"NEWS_DO_CANCEL", +"NNTP_XPAT_SEND", +"NNTP_XPAT_RESPONSE", +"NNTP_SEARCH", +"NNTP_SEARCH_RESPONSE", +"NNTP_SEARCH_RESULTS", +"NNTP_LIST_PRETTY_NAMES", +"NNTP_LIST_PRETTY_NAMES_RESPONSE", +"NNTP_LIST_XACTIVE_RESPONSE", +"NNTP_LIST_XACTIVE", +"NNTP_LIST_GROUP", +"NNTP_LIST_GROUP_RESPONSE", +"NEWS_DONE", +"NEWS_ERROR", +"NNTP_ERROR", +"NEWS_FREE" +}; + +#endif + +/* structure to hold data about a tcp connection + * to a news host + */ +typedef struct _NNTPConnection { + char *hostname; /* hostname string (may contain :port) */ + PRFileDesc *csock; /* control socket */ + XP_Bool busy; /* is the connection in use already? */ + XP_Bool prev_cache; /* did this connection come from the cache? */ + XP_Bool posting_allowed;/* does this server allow posting */ + XP_Bool default_host; + XP_Bool no_xover; /* xover command is not supported here */ + HG55785 + char *current_group; /* last GROUP command sent on this connection */ + time_t last_used_time; /* last time this conn was used, for conn aging purposes */ +} NNTPConnection; + + +/* structure to hold all the state and data + * for the state machine + */ +typedef struct _NewsConData { + MSG_Pane *pane; + MSG_NewsHost *host; + + StatesEnum next_state; + StatesEnum next_state_after_response; + int type_wanted; /* Article, List, or Group */ + int response_code; /* code returned from NNTP server */ + char *response_txt; /* text returned from NNTP server */ + Bool pause_for_read; /* should we pause for the next read? */ + + char *proxy_server; /* @#$ proxy server hostname */ + XP_Bool proxy_auth_required; /* is auth required */ + XP_Bool sent_proxy_auth; /* have we sent a proxy auth? */ + + XP_Bool newsrc_performed; /* have we done a newsrc update? */ + XP_Bool mode_reader_performed; /* have we sent any cmds to the server yet? */ + +#ifdef XP_WIN + XP_Bool calling_netlib_all_the_time; +#endif + NET_StreamClass * stream; + + NNTPConnection * control_con; /* all the info about a connection */ + + char *data_buf; + int32 data_buf_size; + + /* for group command */ + char *path; /* message id */ + + char *group_name; + int32 first_art; + int32 last_art; + int32 first_possible_art; + int32 last_possible_art; + + int32 num_loaded; /* How many articles we got XOVER lines for. */ + int32 num_wanted; /* How many articles we wanted to get XOVER + lines for. */ + + /* for cancellation: the headers to be used */ + char *cancel_from; + char *cancel_newsgroups; + char *cancel_distribution; + char *cancel_id; + char *cancel_msg_file; + int cancel_status; + + /* variables for ReadNewsRC */ + int32 newsrc_list_index; + int32 newsrc_list_count; + + XP_Bool use_fancy_newsgroup_listing; /* use LIST XACTIVE or LIST */ + XP_Bool destroy_graph_progress; /* do we need to destroy + * graph progress? + */ + + char *output_buffer; /* use it to write out lines */ + + int32 article_num; /* current article number */ + + char *message_id; /* for reading groups */ + char *author; + char *subject; + + int32 original_content_length; /* the content length at the time of + * calling graph progress + */ + + /* random pointer for libmsg state */ + void *xover_parse_state; + void *newsgroup_parse_state; + + TCP_ConData * tcp_con_data; + + void * write_post_data_data; + + XP_Bool some_protocol_succeeded; /* We know we have done some protocol + with this newsserver recently, so don't + respond to an error by closing and + reopening it. */ + char *command_specific_data; + char *current_search; + void *offlineState; /* offline news state machine for article retrieval */ + XP_Bool articleIsOffline; + int previous_response_code; + +} NewsConData; + +static char * last_password = 0; +static char * last_password_hostname = 0; +static char * last_username=0; +static char * last_username_hostname=0; + +/* publicly available function to set the news host + * this will be a useful front end callable routine + */ +PUBLIC void NET_SetNewsHost (const char * value) +{ + /* ### This routine is obsolete and should go away. */ +} + + + +/* set the default number of articles retrieved by news at any one time + */ +PUBLIC void +NET_SetNumberOfNewsArticlesInListing(int32 number) +{ + net_NewsChunkSize = number; +} + +/* Whether we cache XOVER data. NYI. */ +PUBLIC void +NET_SetCacheXOVER(XP_Bool value) +{ +} + + + +PUBLIC void NET_CleanTempXOVERCache(void) +{ +} + + + +PUBLIC void net_graceful_shutdown(PRFileDesc* sock, XP_Bool bVal) +{ + static int32 int_pref = -1; + +#ifdef THIS_IS_DEPRICATED + if (sock == NULL) return; +#endif + + if (int_pref == -1) + PREF_GetIntPref("network.socket_shutdown", &int_pref); + if (int_pref > 0 && int_pref < 4) + { + if (bVal) + /* ??? */ + NET_BlockingWrite(sock, "", 0); + else + PR_Shutdown(sock, (PRShutdownHow)(int_pref-1)); + } +} + +static void net_nntp_close (NNTPConnection *con, int status) +{ + if (!con) + { + PR_ASSERT(FALSE); + return; + } + else if (status != MK_INTERRUPTED) + { + const char *command = "QUIT" CRLF; + status = NET_BlockingWrite (con->csock, command, PL_strlen(command)); + NNTP_LOG_WRITE (command); + if (status >= 0) { + HG51966 + } + PR_Close(con->csock); + } + + FREEIF(con->current_group); +} + +/* user has removed a news host from the UI. + * be sure it has also been removed from the + * connection cache so we renegotiate news host + * capabilities if we try to use it again + */ +PUBLIC void NET_OnNewsHostDeleted (const char *hostName) +{ + NNTPConnection *conn; + XP_List *list = nntp_connection_list; + + while ((conn = (NNTPConnection*) XP_ListNextObject(list)) != NULL) + { + if (!(PL_strcasecmp(conn->hostname, hostName))) + { + net_nntp_close (conn, /* conn->busy ? MK_INTERRUPTED : */ 0); + + list = list->prev ? list->prev : nntp_connection_list; + XP_ListRemoveObject (nntp_connection_list, conn); + FREEIF(conn->hostname); + FREE(conn); + } + } +} + + +/* display HTML error state in the context window + * returns negative error status + */ +PRIVATE int +net_display_html_error_state (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + XP_Bool inMsgPane = FALSE; + int major_opcode = cd->response_code/100; + /* #### maybe make this a dialog box + to do that, just return an error. + */ + + /* ERROR STATE + * Set up the HTML stream + */ + ce->format_out = CLEAR_CACHE_BIT(ce->format_out); + StrAllocCopy(ce->URL_s->content_type, TEXT_HTML); + + /* If we're not in a message pane, don't try to spew HTML + * This allows error reporting from the folder pane, subscribe UI, etc. + */ + if (ce->URL_s->msg_pane) + inMsgPane = (MSG_MESSAGEPANE == MSG_GetPaneType(ce->URL_s->msg_pane)); + + if (ce->format_out == FO_PRESENT && inMsgPane) + { + cd->stream = NET_StreamBuilder(ce->format_out, ce->URL_s, + ce->window_id); + if(!cd->stream) + { + ce->URL_s->error_msg = + NET_ExplainErrorDetails(MK_UNABLE_TO_CONVERT); + return MK_UNABLE_TO_CONVERT; + } + + PR_snprintf(cd->output_buffer, + OUTPUT_BUFFER_SIZE, + XP_GetString(XP_HTML_NEWS_ERROR), + cd->response_txt); + if(ce->status > -1) + ce->status = PUTSTRING(cd->output_buffer); + + if(cd->type_wanted == ARTICLE_WANTED || + cd->type_wanted == CANCEL_WANTED) + { + PL_strcpy(cd->output_buffer, + XP_GetString(XP_HTML_ARTICLE_EXPIRED)); + if(ce->status > -1) + PUTSTRING(cd->output_buffer); + + if (cd->path && *cd->path && ce->status > -1) + { + char *urlScheme; + HG72589 + + PR_snprintf(cd->output_buffer, + OUTPUT_BUFFER_SIZE, + "
<%.512s>", cd->path); + if (cd->article_num > 0) + { + PR_snprintf(cd->output_buffer+PL_strlen(cd->output_buffer), + OUTPUT_BUFFER_SIZE-PL_strlen(cd->output_buffer), + " (%lu)", (unsigned long) cd->article_num); + if (ce->URL_s->msg_pane) + MSG_MarkMessageKeyRead(ce->URL_s->msg_pane, cd->article_num, ""); + } + PUTSTRING(cd->output_buffer); + PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "
\n", urlScheme, cd->control_con->hostname, cd->group_name, XP_GetString(MK_MSG_EXPIRE_NEWS_ARTICLES)); + PUTSTRING(cd->output_buffer); + } + + if(ce->status > -1) + { + COMPLETE_STREAM; + cd->stream = 0; + } + } + else + if (cd->type_wanted == SEARCH_WANTED) + ce->status = net_xpat_send(ce); + + ce->URL_s->expires = 1; + /* ce->URL_s->error_msg = + NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + */ + } + else + { + /* FORMAT_OUT is not FO_PRESENT - so instead of emitting an HTML + error message, present it in a dialog box. */ + + PR_snprintf(cd->output_buffer, /* #### i18n */ + OUTPUT_BUFFER_SIZE, + XP_GetString(MK_NEWS_ERROR_FMT), + cd->response_txt); + ce->URL_s->error_msg = PL_strdup (cd->output_buffer); + } + + /* if the server returned a 400 error then it is an expected + * error. the NEWS_ERROR state will not sever the connection + */ + if(major_opcode == 4) + cd->next_state = NEWS_ERROR; + else + cd->next_state = NNTP_ERROR; + + /* If we ever get an error, let's re-issue the GROUP command + before the next ARTICLE command; the overhead isn't very high, + and weird stuff seems to be happening... */ + FREEIF(cd->control_con->current_group); + + /* cd->control_con->prev_cache = FALSE; to keep if from reconnecting */ + return MK_NNTP_SERVER_ERROR; +} + + +/* gets the response code from the nntp server and the + * response line + * + * returns the TCP return code from the read + */ +PRIVATE int +net_news_response (ActiveEntry * ce) +{ + char * line; + NewsConData * cd = (NewsConData *)ce->con_data; + + ce->status = NET_BufferedReadLine(ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + NNTP_LOG_READ(cd->data_buf); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return(MK_NNTP_SERVER_ERROR); + } + + /* if TCP error of if there is not a full line yet return + */ + if(ce->status < 0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, + PR_GetOSError()); + + /* return TCP error + */ + return MK_TCP_READ_ERROR; + } + else if(!line) + { + return ce->status; + } + + cd->pause_for_read = FALSE; /* don't pause if we got a line */ + + HG43574 + /* almost correct + */ + if(ce->status > 1) + { + ce->bytes_received += ce->status; + FE_GraphProgress(ce->window_id, ce->URL_s, ce->bytes_received, ce->status, ce->URL_s->content_length); + } + + StrAllocCopy(cd->response_txt, line+4); + + cd->previous_response_code = cd->response_code; + + sscanf(line, "%d", &cd->response_code); + + /* authentication required can come at any time + */ +#ifdef CACHE_NEWSGRP_PASSWORD + /* + * This is an effort of trying to cache the username/password + * per newsgroup. It is extremely hard to make it work along with + * nntp voluntary password checking mechanism. We are backing this + * feature out. Instead of touching various of backend msg files + * at this late Dogbert 4.0 beta4 game, the infrastructure will + * remain in the msg library. We only modify codes within this file. + * Maybe one day we will try to do it again. Zzzzz -- jht + */ + if(480 == cd->response_code || 450 == cd->response_code || + 502 == cd->response_code) + { + cd->next_state = NNTP_BEGIN_AUTHORIZE; + if (502 == cd->response_code) { + if (2 == cd->previous_response_code/100) { + if (net_news_last_username_probably_valid) { + net_news_last_username_probably_valid = FALSE; + } + else { + MSG_SetNewsgroupUsername(cd->pane, NULL); + MSG_SetNewsgroupPassword(cd->pane, NULL); + } + } + else { + net_news_last_username_probably_valid = FALSE; + if (NNTP_PASSWORD_RESPONSE == cd->next_state_after_response) { + MSG_SetNewsgroupUsername(cd->pane, NULL); + MSG_SetNewsgroupPassword(cd->pane, NULL); + } + } + + } + } +#else + if (480 == cd->response_code || 450 == cd->response_code) + { + cd->next_state = NNTP_BEGIN_AUTHORIZE; + } + else if (502 == cd->response_code) + { + net_news_last_username_probably_valid = FALSE; + return net_display_html_error_state(ce); + } +#endif + else + { + cd->next_state = cd->next_state_after_response; + } + + return(0); /* everything ok */ +} + +HG43072 + +/* interpret the server response after the connect + * + * returns negative if the server responds unexpectedly + */ +PRIVATE int +net_nntp_login_response (ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + XP_Bool postingAllowed = cd->response_code == 200; + + if((cd->response_code/100)!=2) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_ERROR_MESSAGE, cd->response_txt); + + cd->next_state = NNTP_ERROR; + cd->control_con->prev_cache = FALSE; /* to keep if from reconnecting */ + return MK_BAD_NNTP_CONNECTION; + } + + cd->control_con->posting_allowed = postingAllowed; /* ###phil dead code */ + MSG_SetNewsHostPostingAllowed (cd->host, postingAllowed); + + cd->next_state = NNTP_SEND_MODE_READER; + return(0); /* good */ +} + +PRIVATE int +net_nntp_send_mode_reader (ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + PL_strcpy(cd->output_buffer, "MODE READER" CRLF); + + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_SEND_MODE_READER_RESPONSE; + cd->pause_for_read = TRUE; + + return(ce->status); + +} + +PRIVATE int +net_nntp_send_mode_reader_response (ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + cd->mode_reader_performed = TRUE; + + /* ignore the response code and continue + */ + + if (MSG_GetNewsHostPushAuth(cd->host)) + /* if the news host is set up to require volunteered (pushed) authentication, + * do that before we do anything else + */ + cd->next_state = NNTP_BEGIN_AUTHORIZE; + else + cd->next_state = SEND_LIST_EXTENSIONS; + + return(0); +} + +PRIVATE int net_nntp_send_list_extensions (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + PL_strcpy(cd->output_buffer, "LIST EXTENSIONS" CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = SEND_LIST_EXTENSIONS_RESPONSE; + cd->pause_for_read = TRUE; + return ce->status; +} + +PRIVATE int net_nntp_send_list_extensions_response (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + if (cd->response_code > 200 && cd->response_code < 300) + { + char *line = NULL; + MSG_NewsHost *host = cd->host; + + ce->status = NET_BufferedReadLine (ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return MK_NNTP_SERVER_ERROR; + } + if (!line) + return ce->status; /* no line yet */ + if (ce->status < 0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + /* return TCP error */ + return MK_TCP_READ_ERROR; + } + + if ('.' != line[0]) + MSG_AddNewsExtension (host, line); + else + { + /* tell libmsg that it's ok to ask this news host for extensions */ + MSG_SupportsNewsExtensions (cd->host, TRUE); + + /* all extensions received */ + cd->next_state = SEND_LIST_SEARCHES; + cd->pause_for_read = FALSE; + } + } + else + { + /* LIST EXTENSIONS not recognized + * tell libmsg not to ask for any more extensions and move on to + * the real NNTP command we were trying to do. + */ + MSG_SupportsNewsExtensions (cd->host, FALSE); + cd->next_state = SEND_FIRST_NNTP_COMMAND; + } + + return ce->status; +} + +PRIVATE int net_nntp_send_list_searches (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + if (MSG_QueryNewsExtension (cd->host, "SEARCH")) + { + PL_strcpy(cd->output_buffer, "LIST SEARCHES" CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = SEND_LIST_SEARCHES_RESPONSE; + cd->pause_for_read = TRUE; + } + else + { + /* since SEARCH isn't supported, move on to GET */ + cd->next_state = NNTP_GET_PROPERTIES; + cd->pause_for_read = FALSE; + } + + return ce->status; +} + +PRIVATE int net_nntp_send_list_searches_response (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + char *line = NULL; + ce->status = NET_BufferedReadLine (ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return MK_NNTP_SERVER_ERROR; + } + if (!line) + return ce->status; /* no line yet */ + if (ce->status < 0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + /* return TCP error */ + return MK_TCP_READ_ERROR; + } + + if ('.' != line[0]) + { + MSG_AddSearchableGroup (cd->host, line); + } + else + { + /* all searchable groups received */ + /* LIST SRCHFIELDS is legal if the server supports the SEARCH extension, which */ + /* we already know it does */ + cd->next_state = NNTP_LIST_SEARCH_HEADERS; + cd->pause_for_read = FALSE; + } + + return ce->status; +} + +PRIVATE int net_send_list_search_headers (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + PL_strcpy(cd->output_buffer, "LIST SRCHFIELDS" CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_LIST_SEARCH_HEADERS_RESPONSE; + cd->pause_for_read = TRUE; + + return ce->status; +} + +PRIVATE int net_send_list_search_headers_response (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + MSG_NewsHost *host = cd->host; + + char *line = NULL; + ce->status = NET_BufferedReadLine (ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return MK_NNTP_SERVER_ERROR; + } + if (!line) + return ce->status; /* no line yet */ + if (ce->status < 0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + /* return TCP error */ + return MK_TCP_READ_ERROR; + } + + if ('.' != line[0]) + MSG_AddSearchableHeader (host, line); + else + { + cd->next_state = NNTP_GET_PROPERTIES; + cd->pause_for_read = FALSE; + } + + return ce->status; +} + +PRIVATE int nntp_get_properties (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + if (MSG_QueryNewsExtension (cd->host, "SETGET")) + { + PL_strcpy(cd->output_buffer, "GET" CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_GET_PROPERTIES_RESPONSE; + cd->pause_for_read = TRUE; + } + else + { + /* since GET isn't supported, move on LIST SUBSCRIPTIONS */ + cd->next_state = SEND_LIST_SUBSCRIPTIONS; + cd->pause_for_read = FALSE; + } + return ce->status; +} + +PRIVATE int nntp_get_properties_response (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + char *line = NULL; + ce->status = NET_BufferedReadLine (ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return MK_NNTP_SERVER_ERROR; + } + if (!line) + return ce->status; /* no line yet */ + if (ce->status < 0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + /* return TCP error */ + return MK_TCP_READ_ERROR; + } + + if ('.' != line[0]) + { + char *propertyName = PL_strdup(line); + if (propertyName) + { + char *space = PL_strchr(propertyName, ' '); + if (space) + { + char *propertyValue = space + 1; + *space = '\0'; + MSG_AddPropertyForGet (cd->host, + propertyName, propertyValue); + } + PR_Free(propertyName); + } + } + else + { + /* all GET properties received, move on to LIST SUBSCRIPTIONS */ + cd->next_state = SEND_LIST_SUBSCRIPTIONS; + cd->pause_for_read = FALSE; + } + + return ce->status; +} + +PRIVATE int net_send_list_subscriptions (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + +#ifdef LATER + if (MSG_QueryNewsExtension (cd->host, "LISTSUBSCR")) +#else + if (0) +#endif + { + PL_strcpy(cd->output_buffer, "LIST SUBSCRIPTIONS" CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = SEND_LIST_SUBSCRIPTIONS_RESPONSE; + cd->pause_for_read = TRUE; + } + else + { + /* since LIST SUBSCRIPTIONS isn't supported, move on to real work */ + cd->next_state = SEND_FIRST_NNTP_COMMAND; + cd->pause_for_read = FALSE; + } + + return ce->status; +} + +PRIVATE int net_send_list_subscriptions_response (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + char *line = NULL; + ce->status = NET_BufferedReadLine (ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return MK_NNTP_SERVER_ERROR; + } + if (!line) + return ce->status; /* no line yet */ + if (ce->status < 0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + /* return TCP error */ + return MK_TCP_READ_ERROR; + } + + if ('.' != line[0]) + { +#ifdef LATER + char *urlScheme; + HG56946 + char *url = PR_smprintf ("%s//%s/%s", urlScheme, cd->control_con->hostname, line); + if (url) + MSG_AddSubscribedNewsgroup (cd->pane, url); +#endif + } + else + { + /* all default subscriptions received */ + cd->next_state = SEND_FIRST_NNTP_COMMAND; + cd->pause_for_read = FALSE; + } + + return ce->status; +} + +/* figure out what the first command is and send it + * + * returns the status from the NETWrite + */ +PRIVATE int +net_send_first_nntp_command (ActiveEntry *ce) +{ + char *command=0; + NewsConData * cd = (NewsConData *)ce->con_data; + + if (cd->type_wanted == ARTICLE_WANTED) + { + const char *group = 0; + uint32 number = 0; + + MSG_NewsGroupAndNumberOfID (cd->pane, cd->path, + &group, &number); + if (group && number) + { + cd->article_num = number; + StrAllocCopy (cd->group_name, group); + + if (cd->control_con->current_group && !PL_strcmp (cd->control_con->current_group, group)) + cd->next_state = NNTP_SEND_ARTICLE_NUMBER; + else + cd->next_state = NNTP_SEND_GROUP_FOR_ARTICLE; + + cd->pause_for_read = FALSE; + return 0; + } + } + + if(cd->type_wanted == NEWS_POST && !ce->URL_s->post_data) + { + PR_ASSERT(0); + return(-1); + } + else if(cd->type_wanted == NEWS_POST) + { /* posting to the news group */ + StrAllocCopy(command, "POST"); + } + else if(cd->type_wanted == READ_NEWS_RC) + { + if(ce->URL_s->method == URL_POST_METHOD || + PL_strchr(ce->URL_s->address, '?')) + cd->next_state = NEWS_NEWS_RC_POST; + else + cd->next_state = NEWS_DISPLAY_NEWS_RC; + return(0); + } + else if(cd->type_wanted == NEW_GROUPS) + { + time_t last_update = + MSG_NewsgroupsLastUpdatedTime(cd->host); + char small_buf[64]; + PRExplodedTime expandedTime; + + if(!last_update) + { + + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_NEWSGROUP_SCAN_ERROR); + cd->next_state = NEWS_ERROR; + return(MK_INTERRUPTED); + } + + /* subtract some hours just to be sure */ + last_update -= NEWGROUPS_TIME_OFFSET; + + { + int64 secToUSec, timeInSec, timeInUSec; + LL_I2L(timeInSec, last_update); + LL_I2L(secToUSec, PR_USEC_PER_SEC); + LL_MUL(timeInUSec, timeInSec, secToUSec); + PR_ExplodeTime(timeInUSec, PR_LocalTimeParameters, &expandedTime); + } + PR_FormatTimeUSEnglish(small_buf, sizeof(small_buf), + "NEWGROUPS %y%m%d %H%M%S", &expandedTime); + + StrAllocCopy(command, small_buf); + + } + else if(cd->type_wanted == LIST_WANTED) + { + + cd->use_fancy_newsgroup_listing = FALSE; + if (MSG_NewsgroupsLastUpdatedTime(cd->host)) + { + cd->next_state = DISPLAY_NEWSGROUPS; + return(0); + } + else + { +#ifdef BUG_21013 + if(!FE_Confirm(ce->window_id, XP_GetString(XP_CONFIRM_SAVE_NEWSGROUPS))) + { + cd->next_state = NEWS_ERROR; + return(MK_INTERRUPTED); + } +#endif /* BUG_21013 */ + + if (MSG_QueryNewsExtension(cd->host, "XACTIVE")) + { + StrAllocCopy(command, "LIST XACTIVE"); + cd->use_fancy_newsgroup_listing = TRUE; + } + else + { + StrAllocCopy(command, "LIST"); + } + } + } + else if(cd->type_wanted == GROUP_WANTED) + { + /* Don't use MKParse because the news: access URL doesn't follow traditional + * rules. For instance, if the article reference contains a '#', + * the rest of it is lost. + */ + char * slash; + + StrAllocCopy(command, "GROUP "); + + slash = PL_strchr(cd->group_name, '/'); /* look for a slash */ + cd->first_art = 0; + cd->last_art = 0; + if (slash) + { + *slash = '\0'; +#ifdef __alpha + (void) sscanf(slash+1, "%d-%d", &cd->first_art, &cd->last_art); +#else + (void) sscanf(slash+1, "%ld-%ld", &cd->first_art, &cd->last_art); +#endif + } + + StrAllocCopy (cd->control_con->current_group, cd->group_name); + StrAllocCat(command, cd->control_con->current_group); + } + else if (cd->type_wanted == SEARCH_WANTED) + { + if (MSG_QueryNewsExtension(cd->host, "SEARCH")) + { + /* use the SEARCH extension */ + char *slash = PL_strchr (cd->command_specific_data, '/'); + if (slash) + { + char *allocatedCommand = MSG_UnEscapeSearchUrl (slash + 1); + if (allocatedCommand) + { + StrAllocCopy (command, allocatedCommand); + PR_Free(allocatedCommand); + } + } + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_SEARCH_RESPONSE; + } + else + { + /* for XPAT, we have to GROUP into the group before searching */ + StrAllocCopy (command, "GROUP "); + StrAllocCat (command, cd->group_name); + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_XPAT_SEND; + } + } + else if (cd->type_wanted == PRETTY_NAMES_WANTED) + { + if (MSG_QueryNewsExtension(cd->host, "LISTPRETTY")) + { + cd->next_state = NNTP_LIST_PRETTY_NAMES; + return 0; + } + else + { + PR_ASSERT(FALSE); + cd->next_state = NNTP_ERROR; + } + } + else if (cd->type_wanted == PROFILE_WANTED) + { + char *slash = PL_strchr (cd->command_specific_data, '/'); + if (slash) + { + char *allocatedCommand = MSG_UnEscapeSearchUrl (slash + 1); + if (allocatedCommand) + { + StrAllocCopy (command, allocatedCommand); + PR_Free(allocatedCommand); + } + } + cd->next_state = NNTP_RESPONSE; + if (PL_strstr(ce->URL_s->address, "PROFILE NEW")) + cd->next_state_after_response = NNTP_PROFILE_ADD_RESPONSE; + else + cd->next_state_after_response = NNTP_PROFILE_DELETE_RESPONSE; + } + else if (cd->type_wanted == IDS_WANTED) + { + char *slash = PL_strchr(cd->group_name, '/'); /* look for a slash */ + if (slash) + *slash = '\0'; + + cd->next_state = NNTP_LIST_GROUP; + return 0; + } + else /* article or cancel */ + { + if (cd->type_wanted == CANCEL_WANTED) + StrAllocCopy(command, "HEAD "); + else + StrAllocCopy(command, "ARTICLE "); + if (*cd->path != '<') + StrAllocCat(command,"<"); + StrAllocCat(command, cd->path); + if (PL_strchr(command+8, '>')==0) + StrAllocCat(command,">"); + } + + StrAllocCat(command, CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket, command, PL_strlen(command)); + NNTP_LOG_WRITE(command); + PR_Free(command); + + cd->next_state = NNTP_RESPONSE; + if (cd->type_wanted != SEARCH_WANTED && cd->type_wanted != PROFILE_WANTED) + cd->next_state_after_response = SEND_FIRST_NNTP_COMMAND_RESPONSE; + cd->pause_for_read = TRUE; + return(ce->status); + +} /* sent first command */ + + +/* interprets the server response from the first command sent + * + * returns negative if the server responds unexpectedly + */ +PRIVATE int +net_send_first_nntp_command_response (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + int major_opcode = cd->response_code/100; + + if((major_opcode == 3 && cd->type_wanted == NEWS_POST) + || (major_opcode == 2 && cd->type_wanted != NEWS_POST) ) + { + + cd->next_state = SETUP_NEWS_STREAM; + cd->some_protocol_succeeded = TRUE; + + return(0); /* good */ + } + else + { + if (cd->response_code == 411 && cd->type_wanted == GROUP_WANTED) + MSG_GroupNotFound(cd->pane, cd->host, + cd->control_con->current_group, TRUE /* opening group */); + return net_display_html_error_state(ce); + } + + /* start the graph progress indicator + */ + FE_GraphProgressInit(ce->window_id, ce->URL_s, ce->URL_s->content_length); + cd->destroy_graph_progress = TRUE; /* we will need to destroy it */ + cd->original_content_length = ce->URL_s->content_length; + + return(ce->status); +} + + +PRIVATE int +net_send_group_for_article (ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + StrAllocCopy (cd->control_con->current_group, cd->group_name); + PR_snprintf(cd->output_buffer, + OUTPUT_BUFFER_SIZE, + "GROUP %.512s" CRLF, + cd->control_con->current_group); + + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer, + PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_SEND_GROUP_FOR_ARTICLE_RESPONSE; + cd->pause_for_read = TRUE; + + return(ce->status); +} + +PRIVATE int +net_send_group_for_article_response (ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + /* ignore the response code and continue + */ + cd->next_state = NNTP_SEND_ARTICLE_NUMBER; + + return(0); +} + + +PRIVATE int +net_send_article_number (ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + PR_snprintf(cd->output_buffer, + OUTPUT_BUFFER_SIZE, + "ARTICLE %lu" CRLF, + cd->article_num); + + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer, + PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = SEND_FIRST_NNTP_COMMAND_RESPONSE; + cd->pause_for_read = TRUE; + + return(ce->status); +} + + + + +static char * +news_generate_news_url_fn (const char *dest, void *closure, + MimeHeaders *headers) +{ + ActiveEntry *ce = (ActiveEntry *) closure; + /* NOTE: you can't use NewsConData here, because ce might refer + to a file in the disk cache rather than an NNTP connection. */ + char *prefix = NET_ParseURL(ce->URL_s->address, + (GET_PROTOCOL_PART | GET_HOST_PART)); + char *new_dest = NET_Escape (dest, URL_XALPHAS); + char *result = (char *) PR_Malloc (PL_strlen (prefix) + + (new_dest ? PL_strlen (new_dest) : 0) + + 40); + if (result && prefix) + { + PL_strcpy (result, prefix); + if (prefix[PL_strlen(prefix) - 1] != ':') + /* If there is a host, it must be terminated with a slash. */ + PL_strcat (result, "/"); + PL_strcat (result, new_dest); + } + FREEIF (prefix); + FREEIF (new_dest); + return result; +} + +static char * +news_generate_reference_url_fn (const char *dest, void *closure, + MimeHeaders *headers) +{ + return news_generate_news_url_fn (dest, closure, headers); +} + + + +/* Initiates the stream and sets state for the transfer + * + * returns negative if unable to setup stream + */ +PRIVATE int +net_setup_news_stream (ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + if (cd->type_wanted == NEWS_POST) + { + NET_ClearReadSelect(ce->window_id, ce->socket); + NET_SetConnectSelect(ce->window_id, ce->socket); +#ifdef XP_WIN + cd->calling_netlib_all_the_time = TRUE; +#if 0 + /* This should be handled by NET_SetCallNetlibAllTheTime */ + net_call_all_the_time_count++; +#endif + NET_SetCallNetlibAllTheTime(ce->window_id,"mknews"); +#endif /* XP_WIN */ + + ce->con_sock = ce->socket; + cd->next_state = NNTP_SEND_POST_DATA; + + NET_Progress(ce->window_id, XP_GetString(MK_MSG_DELIV_NEWS)); + } + else if(cd->type_wanted == LIST_WANTED) + { + if (cd->use_fancy_newsgroup_listing) + cd->next_state = NNTP_LIST_XACTIVE_RESPONSE; + else + cd->next_state = NNTP_READ_LIST_BEGIN; + } + else if(cd->type_wanted == GROUP_WANTED) + cd->next_state = NNTP_XOVER_BEGIN; + else if(cd->type_wanted == NEW_GROUPS) + cd->next_state = NNTP_NEWGROUPS_BEGIN; + else if(cd->type_wanted == ARTICLE_WANTED || + cd->type_wanted == CANCEL_WANTED) + cd->next_state = NNTP_BEGIN_ARTICLE; + else if (cd->type_wanted == SEARCH_WANTED) + cd->next_state = NNTP_XPAT_SEND; + else if (cd->type_wanted == PRETTY_NAMES_WANTED) + cd->next_state = NNTP_LIST_PRETTY_NAMES; + else if (cd->type_wanted == PROFILE_WANTED) + { + if (PL_strstr(ce->URL_s->address, "PROFILE NEW")) + cd->next_state = NNTP_PROFILE_ADD; + else + cd->next_state = NNTP_PROFILE_DELETE; + } + else + { + PR_ASSERT(0); + return -1; + } + + return(0); /* good */ +} + +/* This doesn't actually generate HTML - but it is our hook into the + message display code, so that we can get some values out of it + after the headers-to-be-displayed have been parsed. + */ +static char * +news_generate_html_header_fn (const char *dest, void *closure, + MimeHeaders *headers) +{ + ActiveEntry *ce = (ActiveEntry *) closure; + /* NOTE: you can't always use NewsConData here if, because ce might + refer to a file in the disk cache rather than an NNTP connection. */ + NewsConData *cd = 0; + + PR_ASSERT(ce->protocol == NEWS_TYPE_URL || + ce->protocol == FILE_CACHE_TYPE_URL || + ce->protocol == MEMORY_CACHE_TYPE_URL); + if (ce->protocol == NEWS_TYPE_URL) + cd = (NewsConData *)ce->con_data; + + if (cd && cd->type_wanted == CANCEL_WANTED) + { + /* Save these for later (used in NEWS_DO_CANCEL state.) */ + cd->cancel_from = + MimeHeaders_get(headers, HEADER_FROM, FALSE, FALSE); + cd->cancel_newsgroups = + MimeHeaders_get(headers, HEADER_NEWSGROUPS, FALSE, TRUE); + cd->cancel_distribution = + MimeHeaders_get(headers, HEADER_DISTRIBUTION, FALSE, FALSE); + cd->cancel_id = + MimeHeaders_get(headers, HEADER_MESSAGE_ID, FALSE, FALSE); + } + else + { + MSG_Pane *messagepane = ce->URL_s->msg_pane; + if (!messagepane) + { + NNTP_LOG_NOTE(("news_generate_html_header_fn: url->msg_pane NULL for URL: %s", ce->URL_s->address)); + messagepane = MSG_FindPane(ce->window_id, MSG_MESSAGEPANE); + } + PR_ASSERT (!cd || cd->type_wanted == ARTICLE_WANTED); + + if (messagepane) + MSG_ActivateReplyOptions (messagepane, headers); + } + return 0; +} + + + +XP_Bool NET_IsNewsMessageURL (const char *url); + +int +net_InitializeNewsFeData (ActiveEntry * ce) +{ + MimeDisplayOptions *opt; + + if (!NET_IsNewsMessageURL (ce->URL_s->address)) + { + PR_ASSERT(0); + return -1; + } + + if (ce->URL_s->fe_data) + { + PR_ASSERT(0); + return -1; + } + + opt = PR_NEW (MimeDisplayOptions); + if (!opt) return MK_OUT_OF_MEMORY; + memset (opt, 0, sizeof(*opt)); + + opt->generate_reference_url_fn = news_generate_reference_url_fn; + opt->generate_news_url_fn = news_generate_news_url_fn; + opt->generate_header_html_fn = 0; + opt->generate_post_header_html_fn = news_generate_html_header_fn; + opt->html_closure = ce; + + ce->URL_s->fe_data = opt; + return 0; +} + + +PRIVATE int +net_begin_article(ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + if (cd->type_wanted != ARTICLE_WANTED && + cd->type_wanted != CANCEL_WANTED) + return 0; + + /* Set up the HTML stream + */ + FREEIF (ce->URL_s->content_type); + ce->URL_s->content_type = PL_strdup (MESSAGE_RFC822); + +#ifdef NO_ARTICLE_CACHEING + ce->format_out = CLEAR_CACHE_BIT (ce->format_out); +#endif + + if (cd->type_wanted == CANCEL_WANTED) + { + PR_ASSERT(ce->format_out == FO_PRESENT); + ce->format_out = FO_PRESENT; + } + + /* Only put stuff in the fe_data if this URL is going to get + passed to MIME_MessageConverter(), since that's the only + thing that knows what to do with this structure. */ + if (CLEAR_CACHE_BIT(ce->format_out) == FO_PRESENT) + { + ce->status = net_InitializeNewsFeData (ce); + if (ce->status < 0) + { + /* #### what error message? */ + return ce->status; + } + } + + cd->stream = NET_StreamBuilder(ce->format_out, ce->URL_s, ce->window_id); + PR_ASSERT (cd->stream); + if (!cd->stream) return -1; + + cd->next_state = NNTP_READ_ARTICLE; + + return 0; +} + + +PRIVATE int +net_news_begin_authorize(ActiveEntry * ce) +{ + char * command = 0; + NewsConData * cd = (NewsConData *)ce->con_data; + char * username = 0, * munged_username = 0; + char * cp; + +#ifdef CACHE_NEWSGRP_PASSWORD + /* reuse cached username from newsgroup folder info*/ + if (cd->pane && + (!net_news_last_username_probably_valid || + (last_username_hostname && + PL_strcasecmp(last_username_hostname, cd->control_con->hostname)))) { + username = HG63218 (MSG_GetNewsgroupUsername(cd->pane)); + if (username && last_username && + !PL_strcmp (username, last_username) && + (cd->previous_response_code == 281 || + cd->previous_response_code == 250 || + cd->previous_response_code == 211)) { + FREEIF (username); + MSG_SetNewsgroupUsername(cd->pane, NULL); + MSG_SetNewsgroupPassword(cd->pane, NULL); + } + } +#endif + + if (cd->pane) + { + /* Following a snews://username:password@newhost.domain.com/newsgroup.topic + * backend calls MSG_Master::FindNewsHost() to locate the folderInfo and setting + * the username/password to the newsgroup folderInfo + */ + username = HG63218 (MSG_GetNewsgroupUsername(cd->pane)); + if (username && *username) + { + StrAllocCopy(last_username, username); + StrAllocCopy(last_username_hostname, cd->control_con->hostname); + /* use it for only once */ + MSG_SetNewsgroupUsername(cd->pane, NULL); + } + else { + /* empty username; free and clear it so it will work with + * our logic + */ + FREEIF(username); + } + } + + /* If the URL/cd->control_con->hostname contains @ this must be triggered + * from the bookmark. Use the embed username if we could. + */ + if ((cp = PL_strchr(cd->control_con->hostname, '@')) != NULL) + { + /* in this case the username and possibly + * the password are in the URL + */ + char * colon; + *cp = '\0'; + + colon = PL_strchr(cd->control_con->hostname, ':'); + if(colon) + *colon = '\0'; + + StrAllocCopy(username, cd->control_con->hostname); + StrAllocCopy(last_username, cd->control_con->hostname); + StrAllocCopy(last_username_hostname, cp+1); + + *cp = '@'; + + if(colon) + *colon = ':'; + } + /* reuse global saved username if we think it is + * valid + */ + if (!username && net_news_last_username_probably_valid) + { + if( last_username_hostname && + !PL_strcasecmp(last_username_hostname, cd->control_con->hostname) ) + StrAllocCopy(username, last_username); + else + net_news_last_username_probably_valid = FALSE; + } + + + if (!username) { +#if defined(CookiesAndSignons) + username = SI_Prompt(ce->window_id, + XP_GetString(XP_PROMPT_ENTER_USERNAME), + "", + cd->control_con->hostname); + +#else + username = FE_Prompt(ce->window_id, + XP_GetString(XP_PROMPT_ENTER_USERNAME), + username ? username : ""); +#endif + + /* reset net_news_last_username_probably_valid to false */ + net_news_last_username_probably_valid = FALSE; + if(!username) { + ce->URL_s->error_msg = + NET_ExplainErrorDetails( MK_NNTP_AUTH_FAILED, "Aborted by user"); + return(MK_NNTP_AUTH_FAILED); + } + else { + StrAllocCopy(last_username, username); + StrAllocCopy(last_username_hostname, cd->control_con->hostname); + } + } + +#ifdef CACHE_NEWSGRP_PASSWORD + if (cd->pane && !MSG_GetNewsgroupUsername(cd->pane)) { + munged_username = HG64643 (username); + MSG_SetNewsgroupUsername(cd->pane, munged_username); + PR_FreeIF(munged_username); + } +#endif + + StrAllocCopy(command, "AUTHINFO user "); + StrAllocCat(command, username); + StrAllocCat(command, CRLF); + + ce->status = (int) NET_BlockingWrite(ce->socket, command, PL_strlen(command)); + NNTP_LOG_WRITE(command); + + FREE(command); + FREE(username); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_AUTHORIZE_RESPONSE;; + + cd->pause_for_read = TRUE; + + return ce->status; +} + +PRIVATE int +net_news_authorize_response(ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + if (281 == cd->response_code || 250 == cd->response_code) + { + /* successful login */ + + /* If we're here because the host demanded authentication before we + * even sent a single command, then jump back to the beginning of everything + */ + if (!cd->mode_reader_performed) + cd->next_state = NNTP_SEND_MODE_READER; + /* If we're here because the host needs pushed authentication, then we + * should jump back to SEND_LIST_EXTENSIONS + */ + else if (MSG_GetNewsHostPushAuth(cd->host)) + cd->next_state = SEND_LIST_EXTENSIONS; + else + /* Normal authentication */ + cd->next_state = SEND_FIRST_NNTP_COMMAND; + + net_news_last_username_probably_valid = TRUE; + return(0); + } + else if (381 == cd->response_code) + { + /* password required + */ + char * command = 0; + char * password = 0, * munged_password = 0; + char * cp; + + if (cd->pane) + { + password = HG63218 (MSG_GetNewsgroupPassword(cd->pane)); + /* use it only once */ + MSG_SetNewsgroupPassword(cd->pane, NULL); + } + + if (net_news_last_username_probably_valid + && last_password + && last_password_hostname + && !PL_strcasecmp(last_password_hostname, cd->control_con->hostname)) + { +#ifdef CACHE_NEWSGRP_PASSWORD + if (cd->pane) + password = HG63218(MSG_GetNewsgroupPassword(cd->pane)); +#else + StrAllocCopy(password, last_password); +#endif + } + else if ((cp = PL_strchr(cd->control_con->hostname, '@')) != NULL) + { + /* in this case the username and possibly + * the password are in the URL + */ + char * colon; + *cp = '\0'; + + colon = PL_strchr(cd->control_con->hostname, ':'); + if(colon) + { + *colon = '\0'; + + StrAllocCopy(password, colon+1); + StrAllocCopy(last_password, colon+1); + StrAllocCopy(last_password_hostname, cp+1); + + *colon = ':'; + } + + *cp = '@'; + + } + if (!password) { + password = +#if defined(CookiesAndSignons) + password = SI_PromptPassword + (ce->window_id, + XP_GetString + (XP_PLEASE_ENTER_A_PASSWORD_FOR_NEWS_SERVER_ACCESS), + cd->control_con->hostname, + TRUE, TRUE); +#else + FE_PromptPassword(ce->window_id, XP_GetString( + XP_PLEASE_ENTER_A_PASSWORD_FOR_NEWS_SERVER_ACCESS ) ); +#endif + net_news_last_username_probably_valid = FALSE; + } + + if(!password) { + ce->URL_s->error_msg = + NET_ExplainErrorDetails(MK_NNTP_AUTH_FAILED, "Aborted by user"); + return(MK_NNTP_AUTH_FAILED); + } + else { + StrAllocCopy(last_password, password); + StrAllocCopy(last_password_hostname, cd->control_con->hostname); + } + +#ifdef CACHE_NEWSGRP_PASSWORD + if (cd->pane && !MSG_GetNewsgroupPassword(cd->pane)) { + munged_password = HG64643(password); + MSG_SetNewsgroupPassword(cd->pane, munged_password); + PR_FreeIF(munged_password); + } +#endif + + StrAllocCopy(command, "AUTHINFO pass "); + StrAllocCat(command, password); + StrAllocCat(command, CRLF); + + ce->status = (int) NET_BlockingWrite(ce->socket, command, PL_strlen(command)); + NNTP_LOG_WRITE(command); + + FREE(command); + FREE(password); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_PASSWORD_RESPONSE; + cd->pause_for_read = TRUE; + + return ce->status; + } + else + { + ce->URL_s->error_msg = NET_ExplainErrorDetails( + MK_NNTP_AUTH_FAILED, + cd->response_txt ? cd->response_txt : ""); +#ifdef CACHE_NEWSGRP_PASSWORD + if (cd->pane) + MSG_SetNewsgroupUsername(cd->pane, NULL); +#endif + net_news_last_username_probably_valid = FALSE; + + return(MK_NNTP_AUTH_FAILED); + } + + PR_ASSERT(0); /* should never get here */ + return(-1); + +} + +PRIVATE int +net_news_password_response(ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + if (281 == cd->response_code || 250 == cd->response_code) + { + /* successful login */ + + /* If we're here because the host demanded authentication before we + * even sent a single command, then jump back to the beginning of everything + */ + if (!cd->mode_reader_performed) + cd->next_state = NNTP_SEND_MODE_READER; + /* If we're here because the host needs pushed authentication, then we + * should jump back to SEND_LIST_EXTENSIONS + */ + else if (MSG_GetNewsHostPushAuth(cd->host)) + cd->next_state = SEND_LIST_EXTENSIONS; + else + /* Normal authentication */ + cd->next_state = SEND_FIRST_NNTP_COMMAND; + + net_news_last_username_probably_valid = TRUE; + MSG_ResetXOVER( ce->URL_s->msg_pane, &cd->xover_parse_state ); + return(0); + } + else + { + ce->URL_s->error_msg = NET_ExplainErrorDetails( + MK_NNTP_AUTH_FAILED, + cd->response_txt ? cd->response_txt : ""); +#ifdef CACHE_NEWSGRP_PASSWORD + if (cd->pane) + MSG_SetNewsgroupPassword(cd->pane, NULL); +#endif + return(MK_NNTP_AUTH_FAILED); + } + + PR_ASSERT(0); /* should never get here */ + return(-1); +} + +PRIVATE int +net_display_newsgroups (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + cd->next_state = NEWS_DONE; + cd->pause_for_read = FALSE; + + NNTP_LOG_NOTE(("about to display newsgroups. path: %s",cd->path)); + +#if 0 + /* #### Now ignoring "news:alt.fan.*" + Need to open the root tree of the default news host and keep + opening one child at each level until we've exhausted the + wildcard... + */ + if(rv < 0) + return(rv); + else +#endif + return(MK_DATA_LOADED); /* all finished */ +} + +PRIVATE int +net_newgroups_begin (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + cd->next_state = NNTP_NEWGROUPS; + NET_Progress(ce->window_id, XP_GetString(XP_PROGRESS_RECEIVE_NEWSGROUP)); + + ce->bytes_received = 0; + + return(ce->status); + +} + +PRIVATE int +net_process_newgroups (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + char *line, *s, *s1=NULL, *s2=NULL, *flag=NULL; + int32 oldest, youngest; + + ce->status = NET_BufferedReadLine(ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return(MK_NNTP_SERVER_ERROR); + } + + if(!line) + return(ce->status); /* no line yet */ + + if(ce->status<0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + + /* return TCP error + */ + return MK_TCP_READ_ERROR; + } + + /* End of list? + */ + if (line[0]=='.' && line[1]=='\0') + { + cd->pause_for_read = FALSE; + if (MSG_QueryNewsExtension(cd->host, "XACTIVE")) + { + cd->group_name = MSG_GetFirstGroupNeedingExtraInfo(cd->host); + if (cd->group_name) + { + cd->next_state = NNTP_LIST_XACTIVE; +#ifdef DEBUG_bienvenu1 + PR_LogPrint("listing xactive for %s\n", cd->group_name); +#endif + return 0; + } + } + cd->next_state = NEWS_DONE; + + if(ce->bytes_received == 0) + { + /* #### no new groups */ + } + + if(ce->status > 0) + return MK_DATA_LOADED; + else + return ce->status; + } + else if (line [0] == '.' && line [1] == '.') + /* The NNTP server quotes all lines beginning with "." by doubling it. */ + line++; + + /* almost correct + */ + if(ce->status > 1) + { + ce->bytes_received += ce->status; + FE_GraphProgress(ce->window_id, ce->URL_s, ce->bytes_received, ce->status, ce->URL_s->content_length); + } + + /* format is "rec.arts.movies.past-films 7302 7119 y" + */ + s = PL_strchr (line, ' '); + if (s) + { + *s = 0; + s1 = s+1; + s = PL_strchr (s1, ' '); + if (s) + { + *s = 0; + s2 = s+1; + s = PL_strchr (s2, ' '); + if (s) + { + *s = 0; + flag = s+1; + } + } + } + youngest = s2 ? atol(s1) : 0; + oldest = s1 ? atol(s2) : 0; + + ce->bytes_received++; /* small numbers of groups never seem + * to trigger this + */ + + MSG_AddNewNewsGroup(ce->URL_s->msg_pane, cd->host, + line, oldest, youngest, flag, FALSE); + if (MSG_QueryNewsExtension(cd->host, "XACTIVE")) + { + MSG_SetGroupNeedsExtraInfo(cd->host, line, TRUE); + } + return(ce->status); +} + + +/* Ahhh, this like print's out the headers and stuff + * + * always returns 0 + */ +PRIVATE int +net_read_news_list_begin (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + cd->next_state = NNTP_READ_LIST; + + NET_Progress(ce->window_id, XP_GetString(XP_PROGRESS_RECEIVE_NEWSGROUP)); + + return(ce->status); + + +} + +/* display a list of all or part of the newsgroups list + * from the news server + */ +PRIVATE int +net_read_news_list (ActiveEntry *ce) +{ + char * line; + char * description; + int i=0; + NewsConData * cd = (NewsConData *)ce->con_data; + + ce->status = NET_BufferedReadLine(ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*) &cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return(MK_NNTP_SERVER_ERROR); + } + + if(!line) + return(ce->status); /* no line yet */ + + if(ce->status<0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + + /* return TCP error + */ + return MK_TCP_READ_ERROR; + } + + /* End of list? */ + if (line[0]=='.' && line[1]=='\0') + { + if (MSG_QueryNewsExtension(cd->host, "LISTPNAMES")) + { + cd->next_state = NNTP_LIST_PRETTY_NAMES; + } + else + { + cd->next_state = DISPLAY_NEWSGROUPS; + } + cd->pause_for_read = FALSE; + + return 0; + } + else if (line [0] == '.' && line [1] == '.') + /* The NNTP server quotes all lines beginning with "." by doubling it. */ + line++; + + /* almost correct + */ + if(ce->status > 1) + { + ce->bytes_received += ce->status; + FE_GraphProgress(ce->window_id, ce->URL_s, ce->bytes_received, ce->status, ce->URL_s->content_length); + } + + /* find whitespace seperator if it exits */ + for(i=0; line[i] != '\0' && !NET_IS_SPACE(line[i]); i++) + ; /* null body */ + + if(line[i] == '\0') + description = &line[i]; + else + description = &line[i+1]; + + line[i] = 0; /* terminate group name */ + + /* store all the group names + */ + MSG_AddNewNewsGroup(ce->URL_s->msg_pane, cd->host, + line, 0, 0, "", FALSE); + + + return(ce->status); +} + + + + +/* start the xover command + */ +PRIVATE int +net_read_xover_begin (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + int32 count; /* Response fields */ + + /* Make sure we never close and automatically reopen the connection at this + point; we'll confuse libmsg too much... */ + + cd->some_protocol_succeeded = TRUE; + + /* We have just issued a GROUP command and read the response. + Now parse that response to help decide which articles to request + xover data for. + */ + sscanf(cd->response_txt, +#ifdef __alpha + "%d %d %d", +#else + "%ld %ld %ld", +#endif + &count, + &cd->first_possible_art, + &cd->last_possible_art); + + /* We now know there is a summary line there; make sure it has the + right numbers in it. */ + ce->status = MSG_DisplaySubscribedGroup(cd->pane, + cd->host, + cd->group_name, + cd->first_possible_art, + cd->last_possible_art, + count, TRUE); + if (ce->status < 0) return ce->status; + + cd->num_loaded = 0; + cd->num_wanted = net_NewsChunkSize > 0 ? net_NewsChunkSize : 1L << 30; + + cd->next_state = NNTP_FIGURE_NEXT_CHUNK; + cd->pause_for_read = FALSE; + return 0; +} + + + +PRIVATE int +net_figure_next_chunk(ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + char *host_and_port = NET_ParseURL (ce->URL_s->address, GET_HOST_PART); + + if (!host_and_port) return MK_OUT_OF_MEMORY; + + if (cd->first_art > 0) { + ce->status = MSG_AddToKnownArticles(cd->pane, cd->host, + cd->group_name, + cd->first_art, cd->last_art); + if (ce->status < 0) { + FREEIF (host_and_port); + return ce->status; + } + } + + + if (cd->num_loaded >= cd->num_wanted) { + FREEIF (host_and_port); + cd->next_state = NEWS_PROCESS_XOVER; + cd->pause_for_read = FALSE; + return 0; + } + + + ce->status = MSG_GetRangeOfArtsToDownload(cd->pane, + &cd->xover_parse_state, + cd->host, + cd->group_name, + cd->first_possible_art, + cd->last_possible_art, + cd->num_wanted - cd->num_loaded, + &(cd->first_art), + &(cd->last_art)); + + if (ce->status < 0) { + FREEIF (host_and_port); + return ce->status; + } + + + if (cd->first_art <= 0 || cd->first_art > cd->last_art) { + /* Nothing more to get. */ + FREEIF (host_and_port); + cd->next_state = NEWS_PROCESS_XOVER; + cd->pause_for_read = FALSE; + return 0; + } + + NNTP_LOG_NOTE((" Chunk will be (%ld-%ld)", cd->first_art, cd->last_art)); + + cd->article_num = cd->first_art; + ce->status = MSG_InitXOVER (cd->pane, + cd->host, cd->group_name, + cd->first_art, cd->last_art, + cd->first_possible_art, cd->last_possible_art, + &cd->xover_parse_state); + FREEIF (host_and_port); + + if (ce->status < 0) { + return ce->status; + } + + cd->pause_for_read = FALSE; + if (cd->control_con->no_xover) cd->next_state = NNTP_READ_GROUP; + else cd->next_state = NNTP_XOVER_SEND; + + return 0; +} + +PRIVATE int +net_xover_send (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + PR_snprintf(cd->output_buffer, + OUTPUT_BUFFER_SIZE, + "XOVER %ld-%ld" CRLF, + cd->first_art, + cd->last_art); + + /* printf("XOVER %ld-%ld\n", cd->first_art, cd->last_art); */ + + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_XOVER_RESPONSE; + cd->pause_for_read = TRUE; + + NET_Progress(ce->window_id, XP_GetString(XP_PROGRESS_RECEIVE_LISTARTICLES)); + + return((int) NET_BlockingWrite(ce->socket, + cd->output_buffer, + PL_strlen(cd->output_buffer))); + NNTP_LOG_WRITE(cd->output_buffer); + +} + +/* see if the xover response is going to return us data + * if the proper code isn't returned then assume xover + * isn't supported and use + * normal read_group + */ +PRIVATE int +net_read_xover_response (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + +#ifdef TEST_NO_XOVER_SUPPORT + cd->response_code = 500; /* pretend XOVER generated an error */ +#endif + + if(cd->response_code != 224) + { + /* If we didn't get back "224 data follows" from the XOVER request, + then that must mean that this server doesn't support XOVER. Or + maybe the server's XOVER support is busted or something. So, + in that case, fall back to the very slow HEAD method. + + But, while debugging here at HQ, getting into this state means + something went very wrong, since our servers do XOVER. Thus + the assert. + */ + /*PR_ASSERT (0);*/ + cd->next_state = NNTP_READ_GROUP; + cd->control_con->no_xover = TRUE; + } + else + { + cd->next_state = NNTP_XOVER; + } + + return(0); /* continue */ +} + +/* process the xover list as it comes from the server + * and load it into the sort list. + */ +PRIVATE int +net_read_xover (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + char *line; + + ce->status = NET_BufferedReadLine(ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, + (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + NNTP_LOG_NOTE(("received unexpected TCP EOF!!!! aborting!")); + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return(MK_NNTP_SERVER_ERROR); + } + + if(!line) + { + return(ce->status); /* no line yet or TCP error */ + } + + if(ce->status<0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, + SOCKET_ERRNO); + + /* return TCP error + */ + return MK_TCP_READ_ERROR; + } + + if(line[0] == '.' && line[1] == '\0') + { + cd->next_state = NNTP_FIGURE_NEXT_CHUNK; + cd->pause_for_read = FALSE; + return(0); + } + else if (line [0] == '.' && line [1] == '.') + /* The NNTP server quotes all lines beginning with "." by doubling it. */ + line++; + + /* almost correct + */ + if(ce->status > 1) + { + ce->bytes_received += ce->status; + FE_GraphProgress(ce->window_id, ce->URL_s, ce->bytes_received, ce->status, + ce->URL_s->content_length); + } + + ce->status = MSG_ProcessXOVER (cd->pane, line, &cd->xover_parse_state); + + cd->num_loaded++; + + return ce->status; /* keep going */ +} + + +/* Finished processing all the XOVER data. + */ +PRIVATE int +net_process_xover (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + + /* if (cd->xover_parse_state) { ### dmb - we need a different check */ + ce->status = MSG_FinishXOVER (cd->pane, &cd->xover_parse_state, 0); + PR_ASSERT (!cd->xover_parse_state); + if (ce->status < 0) return ce->status; + + cd->next_state = NEWS_DONE; + + return(MK_DATA_LOADED); +} + + +PRIVATE int net_profile_add (ActiveEntry *ce) +{ +#if 0 + NewsConData *cd = (NewsConData*) ce->con_data; + char *slash = PL_strrchr(ce->URL_s->address, '/'); + if (slash) + { + PL_strcpy (cd->output_buffer, slash + 1); + PL_strcat (cd->output_buffer, CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_PROFILE_ADD_RESPONSE; + cd->pause_for_read = TRUE; + } + else + { + cd->next_state = NNTP_ERROR; + ce->status = -1; + } + + return ce->status; +#else + PR_ASSERT(FALSE); + return -1; +#endif +} + +PRIVATE int net_profile_add_response (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + if (cd->response_code >= 200 && cd->response_code <= 299) + { + MSG_AddProfileGroup (cd->pane, cd->host, cd->response_txt); + cd->next_state = NEWS_DONE; + } + else + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return MK_NNTP_SERVER_ERROR; + } + + return ce->status; +} + +PRIVATE int net_profile_delete (ActiveEntry *ce) +{ +#if 0 + NewsConData *cd = (NewsConData*) ce->con_data; + char *slash = PL_strrchr(ce->URL_s->address, '/'); + if (slash) + { + PL_strcpy (cd->output_buffer, slash + 1); + PL_strcat (cd->output_buffer, CRLF); + ce->status = (int) NET_BlockingWrite(ce->socket,cd->output_buffer,PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_PROFILE_DELETE_RESPONSE; + cd->pause_for_read = TRUE; + } + else + { + cd->next_state = NNTP_ERROR; + ce->status = -1; + } + + return ce->status; +#else + PR_ASSERT(FALSE); + return -1; +#endif +} + +PRIVATE int net_profile_delete_response (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData*) ce->con_data; + + if (cd->response_code >= 200 && cd->response_code <= 299) + cd->next_state = NEWS_DONE; + else + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return MK_NNTP_SERVER_ERROR; + } + + return ce->status; +} + +PRIVATE +int +net_read_news_group (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + if(cd->article_num > cd->last_art) + { /* end of groups */ + + cd->next_state = NNTP_FIGURE_NEXT_CHUNK; + cd->pause_for_read = FALSE; + return(0); + } + else + { + PR_snprintf(cd->output_buffer, + OUTPUT_BUFFER_SIZE, + "HEAD %ld" CRLF, + cd->article_num++); + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_READ_GROUP_RESPONSE; + + cd->pause_for_read = TRUE; + + NNTP_LOG_WRITE(cd->output_buffer); + return((int) NET_BlockingWrite(ce->socket, cd->output_buffer, PL_strlen(cd->output_buffer))); + } +} + +/* See if the "HEAD" command was successful + */ +PRIVATE int +net_read_news_group_response (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + + if (cd->response_code == 221) + { /* Head follows - parse it:*/ + cd->next_state = NNTP_READ_GROUP_BODY; + + if(cd->message_id) + *cd->message_id = '\0'; + + /* Give the message number to the header parser. */ + return MSG_ProcessNonXOVER (cd->pane, cd->response_txt, + &cd->xover_parse_state); + } + else + { + NNTP_LOG_NOTE(("Bad group header found!")); + cd->next_state = NNTP_READ_GROUP; + return(0); + } +} + +/* read the body of the "HEAD" command + */ +PRIVATE int +net_read_news_group_body (ActiveEntry *ce) +{ + NewsConData * cd = (NewsConData *)ce->con_data; + char *line; + + ce->status = NET_BufferedReadLine(ce->socket, &line, &cd->data_buf, + &cd->data_buf_size, (Bool*)&cd->pause_for_read); + + if(ce->status == 0) + { + cd->next_state = NNTP_ERROR; + cd->pause_for_read = FALSE; + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_NNTP_SERVER_ERROR); + return(MK_NNTP_SERVER_ERROR); + } + + /* if TCP error of if there is not a full line yet return + */ + if(!line) + return ce->status; + + if(ce->status < 0) + { + ce->URL_s->error_msg = NET_ExplainErrorDetails(MK_TCP_READ_ERROR, SOCKET_ERRNO); + + /* return TCP error + */ + return MK_TCP_READ_ERROR; + } + + NNTP_LOG_NOTE(("read_group_body: got line: %s|",line)); + + /* End of body? */ + if (line[0]=='.' && line[1]=='\0') + { + cd->next_state = NNTP_READ_GROUP; + cd->pause_for_read = FALSE; + } + else if (line [0] == '.' && line [1] == '.') + /* The NNTP server quotes all lines beginning with "." by doubling it. */ + line++; + + return MSG_ProcessNonXOVER (cd->pane, line, &cd->xover_parse_state); +} + + +PRIVATE int +net_send_news_post_data(ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *) ce->con_data; + + /* returns 0 on done and negative on error + * positive if it needs to continue. + */ + ce->status = NET_WritePostData(ce->window_id, ce->URL_s, + ce->socket, + &cd->write_post_data_data, + TRUE); + + cd->pause_for_read = TRUE; + + if(ce->status == 0) + { + /* normal done + */ + PL_strcpy(cd->output_buffer, CRLF "." CRLF); + NNTP_LOG_WRITE(cd->output_buffer); + ce->status = (int) NET_BlockingWrite(ce->socket, + cd->output_buffer, + PL_strlen(cd->output_buffer)); + NNTP_LOG_WRITE(cd->output_buffer); + + NET_Progress(ce->window_id, + XP_GetString(XP_MESSAGE_SENT_WAITING_NEWS_REPLY)); + + NET_ClearConnectSelect(ce->window_id, ce->socket); +#ifdef XP_WIN + if(cd->calling_netlib_all_the_time) + { + cd->calling_netlib_all_the_time = FALSE; +#if 0 + /* this should be handled by NET_ClearCallNetlibAllTheTime */ + net_call_all_the_time_count--; + if(net_call_all_the_time_count == 0) +#endif + NET_ClearCallNetlibAllTheTime(ce->window_id,"mknews"); + } +#endif + NET_SetReadSelect(ce->window_id, ce->socket); + ce->con_sock = 0; + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_SEND_POST_DATA_RESPONSE; + return(0); + } + + return(ce->status); + +} + + +/* interpret the response code from the server + * after the post is done + */ +PRIVATE int +net_send_news_post_data_response(ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *) ce->con_data; + + + if (cd->response_code != 240) { + ce->URL_s->error_msg = + NET_ExplainErrorDetails(MK_NNTP_ERROR_MESSAGE, + cd->response_txt ? cd->response_txt : ""); + if (cd->response_code == 441 + && MSG_GetPaneType(cd->pane) == MSG_COMPOSITIONPANE + && MSG_IsDuplicatePost(cd->pane) && + MSG_GetCompositionMessageID(cd->pane)) { + /* The news server won't let us post. We suspect that we're submitting + a duplicate post, and that's why it's failing. So, let's go see + if there really is a message out there with the same message-id. + If so, we'll just silently pretend everything went well. */ + PR_snprintf(cd->output_buffer, OUTPUT_BUFFER_SIZE, "STAT %s" CRLF, + MSG_GetCompositionMessageID(cd->pane)); + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NNTP_CHECK_FOR_MESSAGE; + NNTP_LOG_WRITE(cd->output_buffer); + return (int) NET_BlockingWrite(ce->socket, cd->output_buffer, + PL_strlen(cd->output_buffer)); + } + + MSG_ClearCompositionMessageID(cd->pane); /* So that if the user tries + to just post again, we + won't immediately decide + that this was a duplicate + message and ignore the + error. */ + cd->next_state = NEWS_ERROR; + return(MK_NNTP_ERROR_MESSAGE); + } + cd->next_state = NEWS_ERROR; /* even though it worked */ + cd->pause_for_read = FALSE; + return(MK_DATA_LOADED); +} + + +PRIVATE int +net_check_for_message(ActiveEntry* ce) +{ + NewsConData * cd = (NewsConData *) ce->con_data; + + cd->next_state = NEWS_ERROR; + if (cd->response_code >= 220 && cd->response_code <= 223) { + /* Yes, this article is already there, we're all done. */ + return MK_DATA_LOADED; + } else { + /* The article isn't there, so the failure we had earlier wasn't due to + a duplicate message-id. Return the error from that previous + posting attempt (which is already in ce->URL_s->error_msg). */ + MSG_ClearCompositionMessageID(cd->pane); + return MK_NNTP_ERROR_MESSAGE; + } +} + +#define NEWS_GROUP_DISPLAY_FREQ 20 + +PRIVATE int +net_DisplayNewsRC(ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *) ce->con_data; + + if(!cd->newsrc_performed) + { + cd->newsrc_performed = TRUE; + + cd->newsrc_list_count = MSG_GetNewsRCCount(cd->pane, cd->host); + } + + + + FREEIF(cd->control_con->current_group); + cd->control_con->current_group = MSG_GetNewsRCGroup(cd->pane, cd->host); + + + if(cd->control_con->current_group) + { + /* send group command to server + */ + int32 percent; + + PR_snprintf(NET_Socket_Buffer, OUTPUT_BUFFER_SIZE, "GROUP %.512s" CRLF, + cd->control_con->current_group); + ce->status = (int) NET_BlockingWrite(ce->socket, NET_Socket_Buffer, + PL_strlen(NET_Socket_Buffer)); + NNTP_LOG_WRITE(NET_Socket_Buffer); + + percent = (cd->newsrc_list_count) ? + (int32) (100.0 * ( (double)cd->newsrc_list_index / (double)cd->newsrc_list_count )) : + 0; + FE_SetProgressBarPercent (ce->window_id, percent); + + /* only update every 20 groups for speed */ + if ((cd->newsrc_list_count <= NEWS_GROUP_DISPLAY_FREQ) || (cd->newsrc_list_index % NEWS_GROUP_DISPLAY_FREQ) == 0 || + (cd->newsrc_list_index == cd->newsrc_list_count)) + { + char thisGroup[20]; + char totalGroups[20]; + char *statusText; + + PR_snprintf (thisGroup, sizeof(thisGroup), "%ld", (long) cd->newsrc_list_index); + PR_snprintf (totalGroups, sizeof(totalGroups), "%ld", (long) cd->newsrc_list_count); + statusText = PR_smprintf (XP_GetString(XP_THERMO_PERCENT_FORM), thisGroup, totalGroups); + if (statusText) + { + FE_Progress (ce->window_id, statusText); + PR_Free(statusText); + } + } + + cd->newsrc_list_index++; + + cd->pause_for_read = TRUE; + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NEWS_DISPLAY_NEWS_RC_RESPONSE; + } + else + { + if (cd->newsrc_list_count) + { + FE_SetProgressBarPercent (ce->window_id, -1); + cd->newsrc_list_count = 0; + } + else if (cd->response_code == 215) + { + /* + * 5-9-96 jefft + * If for some reason the news server returns an empty + * newsgroups list with a nntp response code 215 -- list of + * newsgroups follows. We set ce->status to MK_EMPTY_NEWS_LIST + * to end the infinite dialog loop. + */ + ce->status = MK_EMPTY_NEWS_LIST; + } + cd->next_state = NEWS_DONE; + + if(ce->status > -1) + return MK_DATA_LOADED; + else + return(ce->status); + } + + return(ce->status); /* keep going */ + +} + +/* Parses output of GROUP command */ +PRIVATE int +net_DisplayNewsRCResponse(ActiveEntry * ce) +{ + NewsConData * cd = (NewsConData *) ce->con_data; + + if(cd->response_code == 211) + { + char *num_arts = 0, *low = 0, *high = 0, *group = 0; + int32 first_art, last_art; + + /* line looks like: + * 211 91 3693 3789 comp.infosystems + */ + + num_arts = cd->response_txt; + low = PL_strchr(num_arts, ' '); + + if(low) + { + first_art = atol(low); + *low++ = '\0'; + high= PL_strchr(low, ' '); + } + if(high) + { + *high++ = '\0'; + group = PL_strchr(high, ' '); + } + if(group) + { + *group++ = '\0'; + /* the group name may be contaminated by "group selected" at + the end. This will be space separated from the group name. + If a space is found in the group name terminate at that + point. */ + strtok(group, " "); + last_art = atol(high); + } + + ce->status = MSG_DisplaySubscribedGroup(cd->pane, + cd->host, + group, + low ? atol(low) : 0, + high ? atol(high) : 0, + atol(num_arts), FALSE); + if (ce->status < 0) + return ce->status; + } + else if (cd->response_code == 411) + { + MSG_GroupNotFound(cd->pane, cd->host, cd->control_con->current_group, FALSE); + } + /* it turns out subscribe ui depends on getting this displaysubscribedgroup call, + even if there was an error. + */ + if(cd->response_code != 211) + { + /* only on news server error or when zero articles + */ + ce->status = MSG_DisplaySubscribedGroup(cd->pane, + cd->host, + cd->control_con->current_group, + 0, 0, 0, FALSE); + + } + + cd->next_state = NEWS_DISPLAY_NEWS_RC; + + return 0; +} + + +PRIVATE int +net_NewsRCProcessPost(ActiveEntry *ce) +{ + return(0); +} + + + +static void +net_cancel_done_cb (MWContext *context, void *data, int status, + const char *file_name) +{ + ActiveEntry *ce = (ActiveEntry *) data; + NewsConData *cd = (NewsConData *) ce->con_data; + cd->cancel_status = status; + PR_ASSERT(status < 0 || file_name); + cd->cancel_msg_file = (status < 0 ? 0 : PL_strdup(file_name)); +} + + +static int +net_start_cancel (ActiveEntry *ce) +{ + NewsConData *cd = (NewsConData *) ce->con_data; + char *command = "POST" CRLF; + + ce->status = (int) NET_BlockingWrite(ce->socket, command, PL_strlen(command)); + NNTP_LOG_WRITE(command); + + cd->next_state = NNTP_RESPONSE; + cd->next_state_after_response = NEWS_DO_CANCEL; + cd->pause_for_read = TRUE; + return (ce->status); +} + + +static int +net_do_cancel (ActiveEntry *ce) +{ + int status = 0; + NewsConData *cd = (NewsConData *) ce->con_data; + char *id, *subject, *newsgroups, *distribution, *other_random_headers, *body; + char *from, *old_from, *news_url; + int L; + MSG_CompositionFields *fields = NULL; + + + /* #### Should we do a more real check than this? If the POST command + didn't respond with "340 Ok", then it's not ready for us to throw a + message at it... But the normal posting code doesn't do this check. + Why? + */ + PR_ASSERT (cd->response_code == 340); + + /* These shouldn't be set yet, since the headers haven't been "flushed" */ + PR_ASSERT (!cd->cancel_id && + !cd->cancel_from && + !cd->cancel_newsgroups && + !cd->cancel_distribution); + + /* Write out a blank line. This will tell mimehtml.c that the headers + are done, and it will call news_generate_html_header_fn which will + notice the fields we're interested in. + */ + PL_strcpy (cd->output_buffer, CRLF); /* CRLF used to be LINEBREAK. + LINEBREAK is platform dependent + and is only