/* Send the accumulated output data(if any) to server, then close socket. * Regardless of the flush, clear both input and output buffer. * This function is only called to either re-open or close the connector. */ static void s_FlushAndDisconnect(SHttpConnector* uuu, int/*bool*/ close, const STimeout* timeout) { size_t w_size; /* store timeouts for later use */ if (timeout) { uuu->oo_timeout = *timeout; uuu->o_timeout = &uuu->oo_timeout; uuu->ww_timeout = *timeout; uuu->w_timeout = &uuu->ww_timeout; } else { uuu->o_timeout = timeout; uuu->w_timeout = timeout; } if (close && uuu->can_connect != eCC_None && !uuu->sock && ((uuu->flags & fHCC_SureFlush) || BUF_Size(uuu->w_buf))) { /* "WRITE" mode and data (or just flag) pending */ s_PreRead(uuu, timeout, eRM_DropUnread); } s_Disconnect(uuu, timeout, eRM_DropUnread); assert(!uuu->sock); /* clear pending output data, if any */ w_size = BUF_Size(uuu->w_buf); if (w_size && BUF_Read(uuu->w_buf, 0, w_size) != w_size) { CORE_LOG_X(18, eLOG_Error, "[HTTP] Cannot drop output buffer"); assert(0); } }
/* Reset/readout input data and close socket */ static EIO_Status s_Disconnect(SHttpConnector* uuu, const STimeout* timeout, EReadMode read_mode) { EIO_Status status = eIO_Success; if (read_mode == eRM_DropUnread) { size_t r_size = BUF_Size(uuu->r_buf); if (r_size && BUF_Read(uuu->r_buf, 0, r_size) != r_size) { CORE_LOG_X(17, eLOG_Error, "[HTTP] Cannot drop input buffer"); assert(0); } } else if ((status = s_PreRead(uuu, timeout, read_mode)) == eIO_Success) { do { char buf[4096]; size_t x_read; status = s_Read(uuu, buf, sizeof(buf), &x_read); if (!BUF_Write(&uuu->r_buf, buf, x_read)) status = eIO_Unknown; } while (status == eIO_Success); if (status == eIO_Closed) status = eIO_Success; } if (uuu->sock) /* s_PreRead() might have dropped the connection already */ s_DropConnection(uuu, timeout); if (uuu->can_connect == eCC_Once) uuu->can_connect = eCC_None; return status; }
/* Read non-header data from connection */ static EIO_Status s_Read(SHttpConnector* uuu, void* buf, size_t size, size_t* n_read) { EIO_Status status; assert(uuu->sock); if (uuu->flags & fHCC_UrlDecodeInput) { /* read and URL-decode */ size_t n_peeked, n_decoded; size_t peek_size = 3 * size; void* peek_buf = malloc(peek_size); /* peek the data */ status= SOCK_Read(uuu->sock,peek_buf,peek_size,&n_peeked,eIO_ReadPeek); if (status != eIO_Success) { assert(!n_peeked); *n_read = 0; } else { if (URL_Decode(peek_buf,n_peeked,&n_decoded,buf,size,n_read)) { /* decode, then discard successfully decoded data from input */ if (n_decoded) { SOCK_Read(uuu->sock,0,n_decoded,&n_peeked,eIO_ReadPersist); assert(n_peeked == n_decoded); uuu->received += n_decoded; status = eIO_Success; } else if (SOCK_Status(uuu->sock, eIO_Read) == eIO_Closed) { /* we are at EOF, and remaining data cannot be decoded */ status = eIO_Unknown; } } else status = eIO_Unknown; if (status != eIO_Success) CORE_LOG_X(16, eLOG_Error, "[HTTP] Cannot URL-decode data"); } free(peek_buf); } else { /* just read, with no URL-decoding */ status = SOCK_Read(uuu->sock, buf, size, n_read, eIO_ReadPlain); uuu->received += *n_read; } if (uuu->expected) { if (uuu->received > uuu->expected) return eIO_Unknown/*received too much*/; if (uuu->expected != (size_t)(-1L)) { if (status == eIO_Closed && uuu->expected > uuu->received) return eIO_Unknown/*received too little*/; } else if (uuu->received) return eIO_Unknown/*received too much*/; } return status; }
/* Unconditionally drop the connection; timeout may specify time allowance */ static void s_DropConnection(SHttpConnector* uuu, const STimeout* timeout) { size_t http_size = BUF_Size(uuu->http); assert(uuu->sock); if (http_size && BUF_Read(uuu->http, 0, http_size) != http_size) { CORE_LOG_X(4, eLOG_Error, "[HTTP] Cannot discard HTTP header buffer"); assert(0); } SOCK_SetTimeout(uuu->sock, eIO_Close, timeout); SOCK_Close(uuu->sock); uuu->sock = 0; }
/* Prepare connector for reading. Open socket if necessary and * make initial connect and send, re-trying if possible until success. * Return codes: * eIO_Success = success, connector is ready for reading (uuu->sock != NULL); * eIO_Timeout = maybe (check uuu->sock) connected and no data yet available; * other code = error, not connected (uuu->sock == NULL). */ static EIO_Status s_PreRead(SHttpConnector* uuu, const STimeout* timeout, EReadMode read_mode) { EIO_Status status; char* retry; for (;;) { status = s_ConnectAndSend(uuu, read_mode); if (!uuu->sock) { assert(status != eIO_Success); break; } if (status != eIO_Success) { if (status != eIO_Timeout || status == SOCK_Status(uuu->sock, eIO_Read)/*pending*/) break; } /* set timeout */ SOCK_SetTimeout(uuu->sock, eIO_Read, timeout); if (!uuu->read_header) break; if ((status = s_ReadHeader(uuu, &retry, read_mode)) == eIO_Success) { size_t w_size = BUF_Size(uuu->w_buf); assert(!uuu->read_header && !retry); /* pending output data no longer needed */ if (BUF_Read(uuu->w_buf, 0, w_size) != w_size) { CORE_LOG_X(15, eLOG_Error, "[HTTP] Cannot discard output buffer"); assert(0); } break; } /* if polling then bail out with eIO_Timeout */ if(status == eIO_Timeout && timeout && !(timeout->sec | timeout->usec)) break; /* HTTP header read error; disconnect and try to use another server */ SOCK_Abort(uuu->sock); s_DropConnection(uuu, 0/*no wait*/); if (!s_Adjust(uuu, &retry, read_mode)) { uuu->can_connect = eCC_None; break; } } return status; }
static void s_SetAsnConn_CloseCb(CONN conn, AsnIoPtr aip) { struct SAsnConn_Cbdata* cbdata = (struct SAsnConn_Cbdata*) malloc(sizeof(*cbdata)); assert( aip ); if ( cbdata ) { SCONN_Callback cb; cbdata->aip = aip; cb.func = s_CloseAsnConn; cb.data = cbdata; CONN_SetCallback(conn, eCONN_OnClose, &cb, &cbdata->cb); } else { CORE_LOG_X(1, eLOG_Error, "Cannot create cleanup callback for ASN conn-based stream"); } }
/* Parse HTTP header */ static EIO_Status s_ReadHeader(SHttpConnector* uuu, char** retry, EReadMode read_mode) { ERetry redirect = eRetry_None; int server_error = 0; int http_status; EIO_Status status; char* header; size_t size; assert(uuu->sock && uuu->read_header); *retry = 0; /* line by line HTTP header input */ for (;;) { /* do we have full header yet? */ size = BUF_Size(uuu->http); if (!(header = (char*) malloc(size + 1))) { CORE_LOGF_X(7, eLOG_Error, ("[HTTP] Cannot allocate header, %lu bytes", (unsigned long) size)); return eIO_Unknown; } verify(BUF_Peek(uuu->http, header, size) == size); header[size] = '\0'; if (size >= 4 && strcmp(&header[size - 4], "\r\n\r\n") == 0) break/*full header captured*/; free(header); status = SOCK_StripToPattern(uuu->sock, "\r\n", 2, &uuu->http, 0); if (status != eIO_Success) { ELOG_Level level; if (status == eIO_Timeout) { const STimeout* tmo = SOCK_GetTimeout(uuu->sock, eIO_Read); if (!tmo) level = eLOG_Error; else if (tmo->sec | tmo->usec) level = eLOG_Warning; else if (read_mode != eRM_WaitCalled) level = eLOG_Trace; else return status; } else level = eLOG_Error; CORE_LOGF_X(8, level, ("[HTTP] Error reading header (%s)", IO_StatusStr(status))); return status; } } /* the entire header has been read */ uuu->read_header = 0/*false*/; status = eIO_Success; if (BUF_Read(uuu->http, 0, size) != size) { CORE_LOG_X(9, eLOG_Error, "[HTTP] Cannot discard HTTP header buffer"); status = eIO_Unknown; assert(0); } /* HTTP status must come on the first line of the response */ if (sscanf(header, "HTTP/%*d.%*d %d ", &http_status) != 1 || !http_status) http_status = -1; if (http_status < 200 || 299 < http_status) { server_error = http_status; if (http_status == 301 || http_status == 302) redirect = eRetry_Redirect; else if (http_status == 401) redirect = eRetry_Authenticate; else if (http_status == 407) redirect = eRetry_ProxyAuthenticate; else if (http_status < 0 || http_status == 403 || http_status == 404) uuu->net_info->max_try = 0; } uuu->code = http_status < 0 ? -1 : http_status; if ((server_error || !uuu->error_header) && uuu->net_info->debug_printout == eDebugPrintout_Some) { /* HTTP header gets printed as part of data logging when uuu->net_info->debug_printout == eDebugPrintout_Data. */ const char* header_header; if (!server_error) header_header = "HTTP header"; else if (uuu->flags & fHCC_KeepHeader) header_header = "HTTP header (error)"; else if (uuu->code == 301 || uuu->code == 302) header_header = "HTTP header (moved)"; else if (!uuu->net_info->max_try) header_header = "HTTP header (unrecoverable error)"; else header_header = "HTTP header (server error, can retry)"; CORE_DATA_X(19, header, size, header_header); } {{ /* parsing "NCBI-Message" tag */ static const char kNcbiMessageTag[] = "\n" HTTP_NCBI_MESSAGE " "; const char* s; for (s = strchr(header, '\n'); s && *s; s = strchr(s + 1, '\n')) { if (strncasecmp(s, kNcbiMessageTag, sizeof(kNcbiMessageTag)-1)==0){ const char* message = s + sizeof(kNcbiMessageTag) - 1; while (*message && isspace((unsigned char)(*message))) message++; if (!(s = strchr(message, '\r'))) s = strchr(message, '\n'); assert(s); do { if (!isspace((unsigned char) s[-1])) break; } while (--s > message); if (message != s) { if (s_MessageHook) { if (s_MessageIssued <= 0) { s_MessageIssued = 1; s_MessageHook(message); } } else { s_MessageIssued = -1; CORE_LOGF_X(10, eLOG_Critical, ("[NCBI-MESSAGE] %.*s", (int)(s - message), message)); } } break; } } }} if (uuu->flags & fHCC_KeepHeader) { if (!BUF_Write(&uuu->r_buf, header, size)) { CORE_LOG_X(11, eLOG_Error, "[HTTP] Cannot keep HTTP header"); status = eIO_Unknown; } free(header); return status; } if (uuu->parse_http_hdr && !(*uuu->parse_http_hdr)(header, uuu->adjust_data, server_error)) { server_error = 1/*fake, but still boolean true*/; } if (redirect == eRetry_Redirect) { /* parsing "Location" pointer */ static const char kLocationTag[] = "\nLocation: "; char* s; for (s = strchr(header, '\n'); s && *s; s = strchr(s + 1, '\n')) { if (strncasecmp(s, kLocationTag, sizeof(kLocationTag) - 1) == 0) { char* location = s + sizeof(kLocationTag) - 1; while (*location && isspace((unsigned char)(*location))) location++; if (!(s = strchr(location, '\r'))) s = strchr(location, '\n'); assert(s); do { if (!isspace((unsigned char) s[-1])) break; } while (--s > location); if (s != location) { size_t len = (size_t)(s - location); memmove(header, location, len); header[len] = '\0'; *retry = header; } break; } } } else if (redirect != eRetry_None) { /* parsing "Authenticate" tags */ static const char kAuthenticateTag[] = "-Authenticate: "; char* s; for (s = strchr(header, '\n'); s && *s; s = strchr(s + 1, '\n')) { if (strncasecmp(s + (redirect == eRetry_Authenticate ? 4 : 6), kAuthenticateTag, sizeof(kAuthenticateTag)-1)==0){ if ((redirect == eRetry_Authenticate && strncasecmp(s, "\nWWW", 4) != 0) || (redirect == eRetry_ProxyAuthenticate && strncasecmp(s, "\nProxy", 6) != 0)) { continue; } /* TODO */ } } } else if (!server_error) { static const char kContentLengthTag[] = "\nContent-Length: "; const char* s; for (s = strchr(header, '\n'); s && *s; s = strchr(s + 1, '\n')) { if (!strncasecmp(s,kContentLengthTag,sizeof(kContentLengthTag)-1)){ const char* expected = s + sizeof(kContentLengthTag) - 1; while (*expected && isspace((unsigned char)(*expected))) expected++; if (!(s = strchr(expected, '\r'))) s = strchr(expected, '\n'); assert(s); do { if (!isspace((unsigned char) s[-1])) break; } while (--s > expected); if (s != expected) { char* e; errno = 0; uuu->expected = (size_t) strtol(expected, &e, 10); if (errno || e != s) uuu->expected = 0; else if (!uuu->expected) uuu->expected = (size_t)(-1L); } break; } } } if (!*retry) free(header); /* skip & printout the content, if server error was flagged */ if (server_error && uuu->net_info->debug_printout == eDebugPrintout_Some) { BUF buf = 0; char* body; SOCK_SetTimeout(uuu->sock, eIO_Read, 0); /* read until EOF */ SOCK_StripToPattern(uuu->sock, 0, 0, &buf, 0); if (!(size = BUF_Size(buf))) { CORE_LOG_X(12, eLOG_Trace, "[HTTP] No HTTP body received with this error"); } else if ((body = (char*) malloc(size)) != 0) { size_t n = BUF_Read(buf, body, size); if (n != size) { CORE_LOGF_X(13, eLOG_Error, ("[HTTP] Cannot read server error " "body from buffer (%lu out of %lu)", (unsigned long) n, (unsigned long) size)); } CORE_DATA_X(20, body, n, "Server error body"); free(body); } else { CORE_LOGF_X(14, eLOG_Error, ("[HTTP] Cannot allocate server error " "body, %lu bytes", (unsigned long) size)); } BUF_Destroy(buf); } return server_error ? eIO_Unknown : status; }
/* Connect to the HTTP server, specified by uuu->net_info's "port:host". * Return eIO_Success only if socket connection has succeeded and uuu->sock * is non-zero. If unsuccessful, try to adjust uuu->net_info by s_Adjust(), * and then re-try the connection attempt. */ static EIO_Status s_Connect(SHttpConnector* uuu, EReadMode read_mode) { EIO_Status status; assert(!uuu->sock); if (uuu->can_connect == eCC_None) { CORE_LOG_X(5, eLOG_Error, "[HTTP] Connector no longer usable"); return eIO_Closed; } /* the re-try loop... */ for (;;) { int/*bool*/ reset_user_header = 0; char* http_user_header = 0; char* null = 0; TSOCK_Flags flags; uuu->w_len = BUF_Size(uuu->w_buf); if (uuu->net_info->http_user_header) http_user_header = strdup(uuu->net_info->http_user_header); if (!uuu->net_info->http_user_header == !http_user_header) { ConnNetInfo_ExtendUserHeader (uuu->net_info, "User-Agent: NCBIHttpConnector" #ifdef NCBI_CXX_TOOLKIT " (C++ Toolkit)" #else " (C Toolkit)" #endif "\r\n"); reset_user_header = 1; } if (uuu->net_info->debug_printout) ConnNetInfo_Log(uuu->net_info, CORE_GetLOG()); flags = (uuu->net_info->debug_printout == eDebugPrintout_Data ? fSOCK_LogOn : fSOCK_LogDefault); if (uuu->net_info->scheme == eURL_Https) flags |= fSOCK_Secure; if (!(uuu->flags & fHCC_NoUpread)) flags |= fSOCK_ReadOnWrite; /* connect & send HTTP header */ if ((status = URL_ConnectEx (uuu->net_info->host, uuu->net_info->port, uuu->net_info->path, uuu->net_info->args, uuu->net_info->req_method, uuu->w_len, uuu->o_timeout, uuu->w_timeout, uuu->net_info->http_user_header, (int/*bool*/)(uuu->flags & fHCC_UrlEncodeArgs), flags, &uuu->sock)) != eIO_Success) { uuu->sock = 0; } if (reset_user_header) { ConnNetInfo_SetUserHeader(uuu->net_info, 0); uuu->net_info->http_user_header = http_user_header; } if (uuu->sock) break/*success*/; /* connection failed, no socket was created */ if (!s_Adjust(uuu, &null, read_mode)) break/*closed*/; } return status; }
const char* CORE_SendMailEx(const char* to, const char* subject, const char* body, const SSendMailInfo* uinfo) { static const STimeout zero = {0, 0}; const SSendMailInfo* info; SSendMailInfo ainfo; char buffer[1024]; SOCK sock = 0; info = uinfo ? uinfo : SendMailInfo_Init(&ainfo); if (info->magic_number != MX_MAGIC_NUMBER) SENDMAIL_RETURN(6, "Invalid magic number"); if ((!to || !*to) && (!info->cc || !*info->cc) && (!info->bcc || !*info->bcc)) { SENDMAIL_RETURN(7, "At least one message recipient must be specified"); } /* Open connection to sendmail */ if (SOCK_Create(info->mx_host, info->mx_port, &info->mx_timeout, &sock) != eIO_Success) { SENDMAIL_RETURN(8, "Cannot connect to sendmail"); } SOCK_SetTimeout(sock, eIO_ReadWrite, &info->mx_timeout); /* Follow the protocol conversation, RFC821 */ if (!SENDMAIL_READ_RESPONSE(220, 0, buffer)) SENDMAIL_RETURN2(9, "Protocol error in connection init", buffer); if ((!(info->mx_options & fSendMail_StripNonFQDNHost) || !SOCK_gethostbyaddr(0, buffer, sizeof(buffer))) && SOCK_gethostname(buffer, sizeof(buffer)) != 0) { SENDMAIL_RETURN(10, "Unable to get local host name"); } if (!s_SockWrite(sock, "HELO ", 0) || !s_SockWrite(sock, buffer, 0) || !s_SockWrite(sock, MX_CRLF, 2)) { SENDMAIL_RETURN(11, "Write error in HELO command"); } if (!SENDMAIL_READ_RESPONSE(250, 0, buffer)) SENDMAIL_RETURN2(12, "Protocol error in HELO command", buffer); if (!s_SockWrite(sock, "MAIL FROM: <", 0) || !s_SockWrite(sock, info->from, s_FromSize(info)) || !s_SockWrite(sock, ">" MX_CRLF, 1 + 2)) { SENDMAIL_RETURN(13, "Write error in MAIL command"); } if (!SENDMAIL_READ_RESPONSE(250, 0, buffer)) SENDMAIL_RETURN2(14, "Protocol error in MAIL command", buffer); if (to && *to) { const char* error = SENDMAIL_SENDRCPT("To", to, buffer); if (error) return error; } if (info->cc && *info->cc) { const char* error = SENDMAIL_SENDRCPT("Cc", info->cc, buffer); if (error) return error; } if (info->bcc && *info->bcc) { const char* error = SENDMAIL_SENDRCPT("Bcc", info->bcc, buffer); if (error) return error; } if (!s_SockWrite(sock, "DATA" MX_CRLF, 0)) SENDMAIL_RETURN(15, "Write error in DATA command"); if (!SENDMAIL_READ_RESPONSE(354, 0, buffer)) SENDMAIL_RETURN2(16, "Protocol error in DATA command", buffer); if (!(info->mx_options & fSendMail_NoMxHeader)) { /* Follow RFC822 to compose message headers. Note that * 'Date:'and 'From:' are both added by sendmail automagically. */ if (!s_SockWrite(sock, "Subject: ", 0) || (subject && !s_SockWrite(sock, subject, 0)) || !s_SockWrite(sock, MX_CRLF, 2)) SENDMAIL_RETURN(17, "Write error in sending subject"); if (to && *to) { if (!s_SockWrite(sock, "To: ", 0) || !s_SockWrite(sock, to, 0) || !s_SockWrite(sock, MX_CRLF, 2)) SENDMAIL_RETURN(18, "Write error in sending To"); } if (info->cc && *info->cc) { if (!s_SockWrite(sock, "Cc: ", 0) || !s_SockWrite(sock, info->cc, 0) || !s_SockWrite(sock, MX_CRLF, 2)) SENDMAIL_RETURN(19, "Write error in sending Cc"); } } else if (subject && *subject) CORE_LOG_X(2, eLOG_Warning, "[SendMail] Subject ignored in as-is messages"); if (!s_SockWrite(sock, "X-Mailer: CORE_SendMail (NCBI " NCBI_SENDMAIL_TOOLKIT " Toolkit)" MX_CRLF, 0)) { SENDMAIL_RETURN(20, "Write error in sending mailer information"); } assert(sizeof(buffer) > sizeof(MX_CRLF) && sizeof(MX_CRLF) >= 3); if (info->header && *info->header) { size_t n = 0, m = strlen(info->header); int/*bool*/ newline = 0/*false*/; while (n < m) { size_t k = 0; if (SOCK_Wait(sock, eIO_Read, &zero) != eIO_Timeout) break; while (k < sizeof(buffer) - sizeof(MX_CRLF)) { if (info->header[n] == '\n') { memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF) - 1); k += sizeof(MX_CRLF) - 1; newline = 1/*true*/; } else { if (info->header[n] != '\r' || info->header[n+1] != '\n') buffer[k++] = info->header[n]; newline = 0/*false*/; } if (++n >= m) break; } buffer[k] = '\0'/*just in case*/; if (!s_SockWrite(sock, buffer, k)) SENDMAIL_RETURN(21, "Write error while sending custom header"); } if (n < m) SENDMAIL_RETURN(22, "Header write error"); if (!newline && !s_SockWrite(sock, MX_CRLF, 2)) SENDMAIL_RETURN(23, "Write error while finalizing custom header"); } if (body) { size_t n = 0, m = info->body_size ? info->body_size : strlen(body); int/*bool*/ newline = 0/*false*/; if (!(info->mx_options & fSendMail_NoMxHeader) && m) { if (!s_SockWrite(sock, MX_CRLF, 2)) SENDMAIL_RETURN(24, "Write error in message body delimiter"); } while (n < m) { size_t k = 0; if (SOCK_Wait(sock, eIO_Read, &zero) != eIO_Timeout) break; while (k < sizeof(buffer) - sizeof(MX_CRLF)) { if (body[n] == '\n') { memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF) - 1); k += sizeof(MX_CRLF) - 1; newline = 1/*true*/; } else { if (body[n] != '\r' || (n+1 < m && body[n+1] != '\n')){ if (body[n] == '.' && (newline || !n)) { buffer[k++] = '.'; buffer[k++] = '.'; } else buffer[k++] = body[n]; } newline = 0/*false*/; } if (++n >= m) break; } buffer[k] = '\0'/*just in case*/; if (!s_SockWrite(sock, buffer, k)) SENDMAIL_RETURN(25, "Write error while sending message body"); } if (n < m) SENDMAIL_RETURN(26, "Body write error"); if ((!newline && m && !s_SockWrite(sock, MX_CRLF, 2)) || !s_SockWrite(sock, "." MX_CRLF, 1 + 2)) { SENDMAIL_RETURN(27, "Write error while finalizing message body"); } } else if (!s_SockWrite(sock, "." MX_CRLF, 1 + 2)) SENDMAIL_RETURN(28, "Write error while finalizing message"); if (!SENDMAIL_READ_RESPONSE(250, 0, buffer)) SENDMAIL_RETURN2(29, "Protocol error in sending message", buffer); if (!s_SockWrite(sock, "QUIT" MX_CRLF, 0)) SENDMAIL_RETURN(30, "Write error in QUIT command"); if (!SENDMAIL_READ_RESPONSE(221, 0, buffer)) SENDMAIL_RETURN2(31, "Protocol error in QUIT command", buffer); SOCK_Close(sock); return 0; }