90f3091998-05-27Henrik Grubbström (Grubba) /* * Line-buffered protocol handling. * * Henrik Grubbström 1998-05-27 */
a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
b2e7122000-12-23Henrik Grubbström (Grubba) //! Simple nonblocking line-oriented I/O.
90f3091998-05-27Henrik Grubbström (Grubba) class simple {
9eaf1d2008-06-28Martin Nilsson  protected object con;
abb0972002-02-14Martin Nilsson  //! The sequence separating lines from eachother. "\r\n" by default.
9eaf1d2008-06-28Martin Nilsson  protected constant line_separator = "\r\n";
90f3091998-05-27Henrik Grubbström (Grubba) 
b2e7122000-12-23Henrik Grubbström (Grubba)  //! If this variable has been set, multiple lines will be accumulated,
cbe8c92003-04-07Martin Nilsson  //! until a line with a single @expr{"."@} (period) is received.
18d0b42000-12-28Henrik Grubbström (Grubba)  //! @[handle_data()] will then be called with the accumulated data //! as the argument.
b2e7122000-12-23Henrik Grubbström (Grubba)  //! //! @note //! @[handle_data()] is one-shot, ie it will be cleared when it is called. //!
cbe8c92003-04-07Martin Nilsson  //! The line with the single @expr{"."@} (period) will not be in the
b2e7122000-12-23Henrik Grubbström (Grubba)  //! accumulated data. //! //! @seealso
230dd22002-11-26Henrik Grubbström (Grubba)  //! @[handle_command()]
b2e7122000-12-23Henrik Grubbström (Grubba)  //! function(string:void) handle_data; //! This function will be called once for every line that is received. //! //! Overload this function as appropriate. //! //! @note //! It will not be called if @[handle_data()] has been set. //! //! @[line] will not contain the line separator. //! //! @seealso //! @[handle_data()] //! void handle_command(string line);
90f3091998-05-27Henrik Grubbström (Grubba) 
9eaf1d2008-06-28Martin Nilsson  protected int timeout; // Idle time before timeout. protected int timeout_time; // Time at which next timeout will occur.
eec5811998-09-12Henrik Grubbström (Grubba) 
b2e7122000-12-23Henrik Grubbström (Grubba)  //! Queue some data to send. //! //! @seealso
68f08c2002-11-26Henrik Grubbström (Grubba)  //! @[handle_command()], @[handle_data()], @[disconnect()]
b2e7122000-12-23Henrik Grubbström (Grubba)  //!
9eaf1d2008-06-28Martin Nilsson  protected void send(string s)
850ab91998-09-28Per Hedbor  { send_q->put(s); con->set_write_callback(write_callback); }
b2e7122000-12-23Henrik Grubbström (Grubba)  //! This function is called when a timeout occurrs.
3524712015-05-26Martin Nilsson  //!
b2e7122000-12-23Henrik Grubbström (Grubba)  //! Overload this function as appropriate. //! //! The default action is to shut down the connection immediately. //! //! @seealso //! @[create()], @[touch_time()] //!
9eaf1d2008-06-28Martin Nilsson  protected void do_timeout()
eec5811998-09-12Henrik Grubbström (Grubba)  { if (con) { catch {
aac6381998-10-16Henrik Grubbström (Grubba)  con->set_nonblocking(0,0,0); // Make sure all callbacks are cleared. }; catch {
eec5811998-09-12Henrik Grubbström (Grubba)  con->close(); }; catch {
ecaf951998-10-12Niels Möller  // FIXME: Don't do this. It will break SSL connections. /nisse
eec5811998-09-12Henrik Grubbström (Grubba)  destruct(con); }; } }
9eaf1d2008-06-28Martin Nilsson  protected void _timeout_cb()
eec5811998-09-12Henrik Grubbström (Grubba)  { if (timeout > 0) { // Timeouts are enabled. int t = time(); if (t >= timeout_time) { // Time out do_timeout(); } else { // Not yet. call_out(_timeout_cb, timeout_time - t); } } }
b2e7122000-12-23Henrik Grubbström (Grubba)  //! Reset the timeout timer. //! //! @seealso //! @[create()], @[do_timeout()] //!
eec5811998-09-12Henrik Grubbström (Grubba)  void touch_time() { if (timeout > 0) { timeout_time = time() + timeout; } }
9eaf1d2008-06-28Martin Nilsson  protected string multi_line_buffer = ""; protected void _handle_command(string line)
90f3091998-05-27Henrik Grubbström (Grubba)  { if (handle_data) { if (line != ".") {
b2e7122000-12-23Henrik Grubbström (Grubba)  multi_line_buffer += line + line_separator;
90f3091998-05-27Henrik Grubbström (Grubba)  } else {
b2e7122000-12-23Henrik Grubbström (Grubba)  function(string:void) handle = handle_data;
90f3091998-05-27Henrik Grubbström (Grubba)  string data = multi_line_buffer; handle_data = 0; multi_line_buffer = ""; handle(data); } } else { handle_command(line); } }
9eaf1d2008-06-28Martin Nilsson  protected string read_buffer = "";
ecaf951998-10-12Niels Möller 
18d0b42000-12-28Henrik Grubbström (Grubba)  //! Read a line from the input. //! //! @returns
cbe8c92003-04-07Martin Nilsson  //! Returns @expr{0@} when more input is needed.
18d0b42000-12-28Henrik Grubbström (Grubba)  //! Returns the requested line otherwise. //! //! @note //! The returned line will not contain the line separator. //! //! @seealso
230dd22002-11-26Henrik Grubbström (Grubba)  //! @[handle_command()], @[line_separator]
18d0b42000-12-28Henrik Grubbström (Grubba)  //!
9eaf1d2008-06-28Martin Nilsson  protected string read_line()
ecaf951998-10-12Niels Möller  {
b2e7122000-12-23Henrik Grubbström (Grubba)  // FIXME: Should probably keep track of where the search ended last time. int i = search(read_buffer, line_separator);
ecaf951998-10-12Niels Möller  if (i == -1) { return 0; }
b2e7122000-12-23Henrik Grubbström (Grubba)  string data = read_buffer[..i-1]; // Not the line separator. read_buffer = read_buffer[i+sizeof(line_separator)..];
ecaf951998-10-12Niels Möller  return data; }
18d0b42000-12-28Henrik Grubbström (Grubba)  //! Called when data has been received. //! //! Overload as appropriate. //! //! Calls the handle callbacks repeatedly until no more lines are available. //! //! @seealso
230dd22002-11-26Henrik Grubbström (Grubba)  //! @[handle_data()], @[handle_command()], @[read_line()]
18d0b42000-12-28Henrik Grubbström (Grubba)  //!
9eaf1d2008-06-28Martin Nilsson  protected void read_callback(mixed ignored, string data)
90f3091998-05-27Henrik Grubbström (Grubba)  {
eec5811998-09-12Henrik Grubbström (Grubba)  touch_time();
90f3091998-05-27Henrik Grubbström (Grubba)  read_buffer += data;
ecaf951998-10-12Niels Möller  string line;
3524712015-05-26Martin Nilsson 
41a3db1998-10-16Niels Möller  while( (line = read_line()) )
ecaf951998-10-12Niels Möller  _handle_command(line);
90f3091998-05-27Henrik Grubbström (Grubba)  }
b2e7122000-12-23Henrik Grubbström (Grubba)  //! Queue of data that is pending to send. //! //! The elements in the queue are either strings with data to send,
cbe8c92003-04-07Martin Nilsson  //! or @expr{0@} (zero) which is the end of file marker. The connection
b2e7122000-12-23Henrik Grubbström (Grubba)  //! will be closed when the end of file marker is reached. //! //! @seealso //! @[send()], @[disconnect()] //!
6723482000-05-07Martin Nilsson  object(ADT.Queue) send_q = ADT.Queue();
90f3091998-05-27Henrik Grubbström (Grubba) 
9eaf1d2008-06-28Martin Nilsson  protected string write_buffer = ""; protected void write_callback(mixed ignored)
90f3091998-05-27Henrik Grubbström (Grubba)  {
eec5811998-09-12Henrik Grubbström (Grubba)  touch_time();
90f3091998-05-27Henrik Grubbström (Grubba)  while (!sizeof(write_buffer)) { if (send_q->is_empty()) { con->set_write_callback(0); return; } else { write_buffer = send_q->get(); if (!write_buffer) { // EOF con->set_write_callback(0); con->close();
ecaf951998-10-12Niels Möller  // FIXME: Don't do this! It will break SSL connections. /nisse
90f3091998-05-27Henrik Grubbström (Grubba)  catch { destruct(con); }; con = 0; return; } } } int w = con->write(write_buffer); if (w > 0) { write_buffer = write_buffer[w..]; if (!sizeof(write_buffer)) { if (send_q->is_empty()) { con->set_write_callback(0); } else { write_buffer = send_q->get(); if (!write_buffer) { // EOF con->set_write_callback(0); con->close(); catch { destruct(con); }; con = 0; } } } } else { // Failed to write! werror("write_callback(): write() failed!\n");
3524712015-05-26Martin Nilsson 
90f3091998-05-27Henrik Grubbström (Grubba)  con->set_write_callback(0); con->close(); con = 0; } }
b2e7122000-12-23Henrik Grubbström (Grubba)  //! Disconnect the connection. //! //! Pushes an end of file marker onto the send queue @[send_q]. //! //! @note //! Note that the actual closing of the connection is delayed //! until all data in the send queue has been sent. //! //! No more data will be read from the other end after this function //! has been called. //!
90f3091998-05-27Henrik Grubbström (Grubba)  void disconnect() { // Delayed disconnect. send_q->put(0); con->set_write_callback(write_callback); con->set_read_callback(0); }
3524712015-05-26Martin Nilsson 
b2e7122000-12-23Henrik Grubbström (Grubba)  //! This function is called when the connection has been closed //! at the other end. //! //! Overload this function as appropriate. //! //! The default action is to shut down the connection on this side //! as well. //!
9eaf1d2008-06-28Martin Nilsson  protected void close_callback()
90f3091998-05-27Henrik Grubbström (Grubba)  { if (handle_data || sizeof(read_buffer) || sizeof(multi_line_buffer)) { werror("close_callback(): Unexpected close!\n"); }
aac6381998-10-16Henrik Grubbström (Grubba)  con->set_nonblocking(0,0,0); // Make sure all callbacks are cleared.
90f3091998-05-27Henrik Grubbström (Grubba)  con->close(); con = 0; }
b2e7122000-12-23Henrik Grubbström (Grubba)  //! Create a simple nonblocking line-based protocol handler. //! //! @[con] is the connection. //! //! @[timeout] is an optional timeout in seconds after which the connection //! will be closed if there has been no data sent or received. //!
cbe8c92003-04-07Martin Nilsson  //! If @[timeout] is @expr{0@} (zero), no timeout will be in effect.
b2e7122000-12-23Henrik Grubbström (Grubba)  //! //! @seealso //! @[touch_time()], @[do_timeout()] //! void create(object(Stdio.File) con, int|void timeout)
90f3091998-05-27Henrik Grubbström (Grubba)  {
8e06a32014-09-30Martin Nilsson  this::con = con; this::timeout = timeout;
eec5811998-09-12Henrik Grubbström (Grubba)  // Start the timeout handler. touch_time(); _timeout_cb();
90f3091998-05-27Henrik Grubbström (Grubba)  con->set_nonblocking(read_callback, 0, close_callback); } };
18d0b42000-12-28Henrik Grubbström (Grubba) //! Nonblocking line-oriented I/O with support for sending SMTP-style codes.
90f3091998-05-27Henrik Grubbström (Grubba) class smtp_style { inherit simple;
18d0b42000-12-28Henrik Grubbström (Grubba)  //! @decl mapping(int:string|array(string)) errorcodes //! //! Mapping from return-code to error-message. //! //! Overload this constant as apropriate. //!
7dc3162001-04-27Henrik Grubbström (Grubba) 
90f3091998-05-27Henrik Grubbström (Grubba)  constant errorcodes = ([]);
18d0b42000-12-28Henrik Grubbström (Grubba)  //! Send an SMTP-style return-code. //! //! @[code] is an SMTP-style return-code. //! //! If @[lines] is omitted, @[errorcodes] will be used to lookup //! an appropriate error-message. //!
cbe8c92003-04-07Martin Nilsson  //! If @[lines] is a string, it will be split on @expr{"\n"@} //! (newline), and the error-code interspersed as appropriate.
18d0b42000-12-28Henrik Grubbström (Grubba)  //! //! @seealso //! @[errorcodes] //!
d500062000-12-28Henrik Grubbström (Grubba)  void send(int(100 .. 999) code, array(string)|string|void lines)
90f3091998-05-27Henrik Grubbström (Grubba)  { lines = lines || errorcodes[code] || "Error"; if (stringp(lines)) { lines /= "\n"; } string init = sprintf("%03d", code); string res = ""; int i; for(i=0; i < sizeof(lines)-1; i++) {
18d0b42000-12-28Henrik Grubbström (Grubba)  res += init + "-" + lines[i] + line_separator;
90f3091998-05-27Henrik Grubbström (Grubba)  }
18d0b42000-12-28Henrik Grubbström (Grubba)  res += init + " " + lines[-1] + line_separator;
90f3091998-05-27Henrik Grubbström (Grubba)  send_q->put(res); con->set_write_callback(write_callback); } };
ecaf951998-10-12Niels Möller 
18d0b42000-12-28Henrik Grubbström (Grubba) //! Nonblocking line-oriented I/O with support for reading literals.
ecaf951998-10-12Niels Möller class imap_style { inherit simple;
18d0b42000-12-28Henrik Grubbström (Grubba)  //! Length in bytes of the literal to read.
ecaf951998-10-12Niels Möller  int literal_length;
18d0b42000-12-28Henrik Grubbström (Grubba)  //! If this variable has been set, @[literal_length] bytes will be //! accumulated, and this function will be called with the resulting data. //! //! @note //! @[handle_literal()] is one-shot, ie it will be cleared when it is called. //! function(string:void) handle_literal; //! This function will be called once for every line that is received. //! //! @note
230dd22002-11-26Henrik Grubbström (Grubba)  //! This API is provided for backward compatibility; overload //! @[handle_command()] instead.
18d0b42000-12-28Henrik Grubbström (Grubba)  //! //! @seealso
230dd22002-11-26Henrik Grubbström (Grubba)  //! @[Protocols.Line.simple()->handle_command()]
18d0b42000-12-28Henrik Grubbström (Grubba)  function(string:void) handle_line;
230dd22002-11-26Henrik Grubbström (Grubba)  //! Function called once for every received line.
18d0b42000-12-28Henrik Grubbström (Grubba)  void handle_command(string line) { handle_line(line); }
41a3db1998-10-16Niels Möller 
9eaf1d2008-06-28Martin Nilsson  protected void read_callback(mixed ignored, string data)
ecaf951998-10-12Niels Möller  { touch_time(); read_buffer += data; while(1) { if (handle_literal) {
ead9722003-01-20Martin Nilsson  if (sizeof(read_buffer) < literal_length)
ecaf951998-10-12Niels Möller  return; string literal = read_buffer[..literal_length - 1]; read_buffer = read_buffer[literal_length..]; function handler = handle_literal; handle_literal = 0; handler(literal); } else {
41a3db1998-10-16Niels Möller  string line = read_line();
ecaf951998-10-12Niels Möller  if (line)
18d0b42000-12-28Henrik Grubbström (Grubba)  handle_command(line);
ecaf951998-10-12Niels Möller  else break; } } }
18d0b42000-12-28Henrik Grubbström (Grubba)  //! Enter literal reading mode. //! //! Sets @[literal_length] and @[handle_literal()]. //! //! @seealso //! @[literal_length], @[handle_literal()] //! void expect_literal(int length, function(string:void) callback)
ecaf951998-10-12Niels Möller  { literal_length = length; handle_literal = callback; } }