summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2010-10-25 23:49:48 +0200
committerAraq <rumpf_a@web.de>2010-10-25 23:49:48 +0200
commit1dd9ec85b28335baae23d1e8d7bdb00a74c16cbf (patch)
tree33d60936af173bdb14c418c296370e74785ce1c5
parentbeb9e3d32579ec9a5f9251f5cb89e857de31c8d9 (diff)
downloadNim-1dd9ec85b28335baae23d1e8d7bdb00a74c16cbf.tar.gz
bugfix: typo in SMTP module; SCGI module finished
-rw-r--r--.gitignore11
-rw-r--r--doc/lib.txt16
-rw-r--r--lib/impure/ssl.nim4
-rw-r--r--lib/pure/scgi.nim140
-rw-r--r--lib/pure/smtp.nim74
-rwxr-xr-xtodo.txt2
-rw-r--r--web/nimrod.ini6
7 files changed, 217 insertions, 36 deletions
diff --git a/.gitignore b/.gitignore
index bdbe1a87b..71bf22caa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,13 @@
 *.o
 nimcache
+lib/nimcache
+tools/nimcache
+tests/nimcache
+tests/accept/run/nimcache
+tests/accept/compile/nimcache
+tests/reject/nimcache
+rod/nimcache
+rod/c2nim/nimcache
+rod/pas2nim/nimcache
+rod/tinyc
+
diff --git a/doc/lib.txt b/doc/lib.txt
index e95551c78..dfbcac9a4 100644
--- a/doc/lib.txt
+++ b/doc/lib.txt
@@ -113,7 +113,10 @@ Internet Protocols and Support
 ------------------------------
 
 * `cgi <cgi.html>`_
-  This module implements helpers for CGI applictions.
+  This module implements helpers for CGI applications.
+
+* `scgi <scgi.html>`_
+  This module implements helpers for SCGI applications.
 
 * `sockets <sockets.html>`_
   This module implements a simple portable type-safe sockets layer.
@@ -128,6 +131,9 @@ Internet Protocols and Support
 * `httpclient <httpclient.html>`_
   This module implements a simple HTTP client.
 
+* `smtp <smtp.html>`_
+  This module implement a simple SMTP client. 
+  
 
 Parsers
 -------
@@ -194,6 +200,9 @@ Cryptography and Hashing
 * `md5 <md5.html>`_
   This module implements the MD5 checksum algorithm.
 
+* `base64 <base64.html>`_
+  This module implements a base64 encoder and decoder.
+
 
 Multimedia support
 ------------------
@@ -251,6 +260,9 @@ Other
   This module contains simple high-level procedures for dealing with the
   Web like loading the contents of a Web page from an URL.
 
+* `ssl <ssl.html>`_
+  This module provides an easy to use sockets-style 
+  Nimrod interface to the OpenSSL library.
 
 
 Wrappers
@@ -425,6 +437,8 @@ Internet Protocols and Support
 * `libcurl <libcurl.html>`_
   Contains a wrapper for the libcurl library.
 
+* `openssl <openssl.html>`_
+  Wrapper for OpenSSL.
 
 Scripting languages
 -------------------
diff --git a/lib/impure/ssl.nim b/lib/impure/ssl.nim
index c1b8276f5..e90488a2b 100644
--- a/lib/impure/ssl.nim
+++ b/lib/impure/ssl.nim
@@ -13,7 +13,7 @@
 import openssl, strutils, os
 
 type
-  TSecureSocket* = object {.final.}
+  TSecureSocket* {.final.} = object
     ssl: PSSL
     bio: PBIO
 
@@ -50,7 +50,7 @@ proc connect*(sock: var TSecureSocket, address: string,
   
   result = SSL_get_verify_result(sock.ssl)
 
-proc recvLine*(sock: TSecureSocket, line: var String): bool =
+proc recvLine*(sock: TSecureSocket, line: var string): bool =
   ## Acts in a similar fashion to the `recvLine` in the sockets module.
   ## Returns false when no data is available to be read.
   ## `Line` must be initialized and not nil!
diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim
new file mode 100644
index 000000000..a203cf14c
--- /dev/null
+++ b/lib/pure/scgi.nim
@@ -0,0 +1,140 @@
+#
+#
+#            Nimrod's Runtime Library
+#        (c) Copyright 2010 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements helper procs for SCGI applictions. Example:
+## 
+## .. code-block:: Nimrod
+##
+##    import strtabs, sockets, scgi
+##
+##    var counter = 0
+##    proc handleRequest(client: TSocket, input: string, 
+##                       headers: PStringTable): bool {.procvar.} =
+##      inc(counter)
+##      client.writeStatusOkTextContent()
+##      client.send("Hello for the $#th time." % $counter & "\c\L")
+##      return false # do not stop processing
+##
+##    run(handleRequest)
+##
+
+import sockets, strutils, os, strtabs
+
+type
+  EScgi* = object of EIO ## the exception that is raised, if a SCGI error occurs
+
+proc scgiError*(msg: string) {.noreturn.} = 
+  ## raises an EScgi exception with message `msg`.
+  var e: ref EScgi
+  new(e)
+  e.msg = msg
+  raise e
+
+proc parseWord(inp: string, outp: var string, start: int): int = 
+  result = start
+  while inp[result] != '\0': inc(result)
+  outp = copy(inp, start, result-1)
+
+proc parseHeaders(s: string, L: int): PStringTable = 
+  result = newStringTable()
+  var i = 0
+  while i < L:
+    var key, val: string
+    i = parseWord(s, key, i)+1
+    i = parseWord(s, val, i)+1
+    result[key] = val
+  if s[i] == ',': inc(i)
+  else: scgiError("',' after netstring expected")
+  
+proc recvChar(s: TSocket): char = 
+  var c: char
+  if recv(s, addr(c), sizeof(c)) == sizeof(c): 
+    result = c
+  
+type
+  TScgiState* {.final.} = object ## SCGI state object
+    server: TSocket
+    bufLen: int
+    client*: TSocket ## the client socket to send data to
+    headers*: PStringTable ## the parsed headers
+    input*: string  ## the input buffer
+    
+proc recvBuffer(s: var TScgiState, L: int) =
+  if L > s.bufLen: 
+    s.bufLen = L
+    s.input = newString(L)
+  if L > 0 and recv(s.client, cstring(s.input), L) != L: 
+    scgiError("could not read all data")
+  setLen(s.input, L)
+  
+proc open*(s: var TScgiState, port = TPort(4000)) = 
+  ## opens a connection.
+  s.bufLen = 4000
+  s.input = newString(s.buflen) # will be reused
+  system.stackTraceNewLine = "<br />\n"
+  
+  s.server = socket()
+  if s.server == InvalidSocket: scgiError("could not open socket")
+  #s.server.connect(connectionName, port)
+  bindAddr(s.server, port)
+  listen(s.server)
+  
+proc close*(s: var TScgiState) = 
+  ## closes the connection.
+  s.server.close()
+
+proc next*(s: var TScgistate) = 
+  ## proceed to the first/next request.
+  s.client = accept(s.server)
+  var L = 0
+  while true:
+    var d = s.client.recvChar()
+    if d notin strutils.digits: 
+      if d != ':': scgiError("':' after length expected")
+      break
+    L = L * 10 + ord(d) - ord('0')  
+  recvBuffer(s, L+1)
+  s.headers = parseHeaders(s.input, L)
+  if s.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected")
+  L = parseInt(s.headers["CONTENT_LENGTH"])
+  recvBuffer(s, L)
+  
+proc writeStatusOkTextContent*(c: TSocket) = 
+  ## sends the following string to the socket `c`::
+  ##
+  ##   Status: 200 OK\r\LContent-Type: text/plain\r\L\r\L
+  ##
+  ## You should send this before sending your HTML page, for example.
+  c.send("Status: 200 OK\r\L" &
+         "Content-Type: text/plain\r\L\r\L")
+
+proc run*(handleRequest: proc (client: TSocket, input: string, 
+                               headers: PStringTable): bool,
+          port = TPort(4000)) = 
+  ## encapsulates the SCGI object and main loop.
+  var s: TScgiState
+  s.open(port)
+  var stop = false
+  while not stop:
+    next(s)
+    stop = handleRequest(s.client, s.input, s.headers)
+    s.client.close()
+  s.close()
+
+when isMainModule:
+  var counter = 0
+  proc handleRequest(client: TSocket, input: string, 
+                     headers: PStringTable): bool {.procvar.} =
+    inc(counter)
+    client.writeStatusOkTextContent()
+    client.send("Hello for the $#th time." % $counter & "\c\L")
+    return false # do not stop processing
+
+  run(handleRequest)
+
diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim
index dcec012eb..88c05aaed 100644
--- a/lib/pure/smtp.nim
+++ b/lib/pure/smtp.nim
@@ -10,7 +10,8 @@
 ## This module implements the SMTP client protocol as specified by RFC 5321, 
 ## this can be used to send mail to any SMTP Server.
 ## 
-## This module also implements the protocol used to format messages, as specified by RFC 2822.
+## This module also implements the protocol used to format messages, 
+## as specified by RFC 2822.
 ## 
 ## Example gmail use:
 ## 
@@ -23,40 +24,49 @@
 ##   smtp.auth("username", "password")
 ##   smtp.sendmail("username@gmail.com", @["foo@gmail.com"], $msg)
 ##   
+## 
+## For SSL support this module relies on the SSL module. If you want to 
+## disable SSL, compile with ``-d:NoSSL``.
+
+import sockets, strutils, strtabs, base64, os
 
-import sockets, strutils, strtabs, ssl, base64, os
+when not defined(noSSL):
+  import ssl
 
 type
-  TSMTP* = object {.final.}
+  TSMTP* {.final.} = object
     sock: TSocket
-    sslSock: TSecureSocket
+    when not defined(noSSL):
+      sslSock: TSecureSocket
     ssl: Bool
     debug: Bool
   
-  TMessage* = object {.final.}
-    msgTo: seq[String]
-    msgCc: seq[String]
-    msgSubject: String
+  TMessage* {.final.} = object
+    msgTo: seq[string]
+    msgCc: seq[string]
+    msgSubject: string
     msgOtherHeaders: PStringTable
-    msgBody: String
+    msgBody: string
   
   EInvalidReply* = object of EBase
   
-proc debugSend(smtp: TSMTP, cmd: String) =
+proc debugSend(smtp: TSMTP, cmd: string) =
   if smtp.debug:
     echo("C:" & cmd)
   if not smtp.ssl:
     smtp.sock.send(cmd)
   else:
-    smtp.sslSock.send(cmd)
+    when not defined(noSSL):
+      smtp.sslSock.send(cmd)
 
-proc debugRecv(smtp: TSMTP): String =
+proc debugRecv(smtp: TSMTP): string =
   var line = ""
   var ret = False
   if not smtp.ssl:
     ret = smtp.sock.recvLine(line)
   else:
-    ret = smtp.sslSock.recvLine(line)
+    when not defined(noSSL):
+      ret = smtp.sslSock.recvLine(line)
   if ret:
     if smtp.debug:
       echo("S:" & line)
@@ -65,7 +75,7 @@ proc debugRecv(smtp: TSMTP): String =
     OSError()
     return ""
 
-proc quitExcpt(smtp: TSMTP, msg: String) =
+proc quitExcpt(smtp: TSMTP, msg: string) =
   smtp.debugSend("QUIT")
   raise newException(EInvalidReply, msg)
 
@@ -74,8 +84,8 @@ proc checkReply(smtp: TSMTP, reply: string) =
   if not line.startswith(reply):
     quitExcpt(smtp, "Expected " & reply & " reply, got: " & line)
 
-proc connect*(address: String, port: int = 25, 
-              ssl: bool = False, debug: bool = False): TSMTP =
+proc connect*(address: string, port = 25, 
+              ssl = false, debug = false): TSMTP =
   ## Establishes a connection with a SMTP server.
   ## May fail with EInvalidReply or with a socket errors.
 
@@ -83,9 +93,13 @@ proc connect*(address: String, port: int = 25,
     result.sock = socket()
     result.sock.connect(address, TPort(port))
   else:
-    result.ssl = True
-    discard result.sslSock.connect(address, port)
-  
+    when not defined(noSSL):
+      result.ssl = True
+      discard result.sslSock.connect(address, port)
+    else:
+      raise newException(EInvalidReply, 
+                         "SMTP module compiled without SSL support")
+
   result.debug = debug
   
   result.checkReply("220")
@@ -93,7 +107,8 @@ proc connect*(address: String, port: int = 25,
   result.checkReply("250")
 
 proc auth*(smtp: TSMTP, username, password: string) =
-  ## Sends an AUTH command to the server to login as the `username` using `password`.
+  ## Sends an AUTH command to the server to login as the `username` 
+  ## using `password`.
   ## May fail with EInvalidReply.
 
   smtp.debugSend("AUTH LOGIN\c\L")
@@ -108,7 +123,8 @@ proc auth*(smtp: TSMTP, username, password: string) =
 proc sendmail*(smtp: TSMTP, fromaddr: string,
                toaddrs: seq[string], msg: string) =
   ## Sends `msg` from `fromaddr` to `toaddr`. 
-  ## Messages may be formed using ``createMessage`` by converting the TMessage into a string.
+  ## Messages may be formed using ``createMessage`` by converting the
+  ## TMessage into a string.
 
   smtp.debugSend("MAIL FROM:<" & fromaddr & ">\c\L")
   smtp.checkReply("250")
@@ -126,8 +142,8 @@ proc sendmail*(smtp: TSMTP, fromaddr: string,
   # quit
   smtp.debugSend("QUIT\c\L")
 
-proc createMessage*(mSubject, mBody: String, mTo, mCc: seq[String],
-                otherHeaders: openarray[tuple[name, value: String]]): TMessage =
+proc createMessage*(mSubject, mBody: string, mTo, mCc: seq[string],
+                otherHeaders: openarray[tuple[name, value: string]]): TMessage =
   ## Creates a new MIME compliant message.
   result.msgTo = mTo
   result.msgCc = mCc
@@ -137,8 +153,8 @@ proc createMessage*(mSubject, mBody: String, mTo, mCc: seq[String],
   for n, v in items(otherHeaders):
     result.msgOtherHeaders[n] = v
 
-proc createMessage*(mSubject, mBody: String, mTo,
-                    mCc: seq[String] = @[]): TMessage =
+proc createMessage*(mSubject, mBody: string, mTo,
+                    mCc: seq[string] = @[]): TMessage =
   ## Alternate version of the above.
   result.msgTo = mTo
   result.msgCc = mCc
@@ -146,18 +162,19 @@ proc createMessage*(mSubject, mBody: String, mTo,
   result.msgBody = mBody
   result.msgOtherHeaders = newStringTable()
 
-proc `$`*(msg: TMessage): String =
+proc `$`*(msg: TMessage): string =
   result = ""
   if msg.msgTo.len() > 0:
     result = "TO: " & msg.msgTo.join(", ") & "\c\L"
   if msg.msgCc.len() > 0:
-    result.add("CC: " & msg.msgTo.join(", ") & "\c\L")
+    result.add("CC: " & msg.msgCc.join(", ") & "\c\L")
   # TODO: Folding? i.e when a line is too long, shorten it...
   result.add("Subject: " & msg.msgSubject & "\c\L")
   for key, value in pairs(msg.msgOtherHeaders):
     result.add(key & ": " & value & "\c\L")
 
-  result.add("\c\L" & msg.msgBody)
+  result.add("\c\L")
+  result.add(msg.msgBody)
   
 
 when isMainModule:
@@ -169,7 +186,6 @@ when isMainModule:
   #smtp.sendmail("root@localhost", @["dominik@localhost"], $msg)
   
   #echo(decode("a17sm3701420wbe.12"))
-  
   var msg = createMessage("Hello from Nimrod's SMTP!", 
                           "Hello!!!!.\n Is this awesome or what?", 
                           @["someone@yahoo.com", "someone@gmail.com"])
diff --git a/todo.txt b/todo.txt
index dc9d1fde4..9240286be 100755
--- a/todo.txt
+++ b/todo.txt
@@ -59,7 +59,7 @@ Library
 - locale support
 - conversion between character sets
 - bignums
-- ftp, smtp (and other internet protocols)
+- ftp (and other internet protocols)
 
 - pdcurses bindings
 - queues additional to streams: have two positions (read/write) instead of one
diff --git a/web/nimrod.ini b/web/nimrod.ini
index f3483ce79..65fc49936 100644
--- a/web/nimrod.ini
+++ b/web/nimrod.ini
@@ -32,15 +32,15 @@ srcdoc: "pure/parsecfg;pure/parsexml;pure/parsecsv;pure/parsesql"
 srcdoc: "pure/streams;pure/terminal;pure/cgi;impure/web;pure/unicode"
 srcdoc: "impure/zipfiles;pure/xmlgen;pure/macros;pure/parseutils;pure/browsers"
 srcdoc: "impure/db_postgres;impure/db_mysql;impure/db_sqlite"
-srcdoc: "pure/httpserver;pure/httpclient"
+srcdoc: "pure/httpserver;pure/httpclient;pure/stmp;impure/ssl"
 srcdoc: "pure/ropes;pure/unidecode/unidecode;pure/xmldom;pure/xmldomparser"
 srcdoc: "pure/xmlparser;pure/htmlparser;pure/xmltree;pure/colors"
-srcdoc: "pure/json;impure/graphics"
+srcdoc: "pure/json;pure/base64;pure/scgi;impure/graphics"
 
 webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup"
 webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc"
 webdoc: "wrappers/python;wrappers/tcl;wrappers/expat;wrappers/pcre"
-webdoc: "wrappers/tre"
+webdoc: "wrappers/tre;wrappers/openssl"
 
 webdoc: "posix/posix;wrappers/odbcsql;impure/dialogs"
 webdoc: "wrappers/zip/zlib;wrappers/zip/libzip"