diff options
author | Araq <rumpf_a@web.de> | 2010-10-25 23:49:48 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2010-10-25 23:49:48 +0200 |
commit | 1dd9ec85b28335baae23d1e8d7bdb00a74c16cbf (patch) | |
tree | 33d60936af173bdb14c418c296370e74785ce1c5 | |
parent | beb9e3d32579ec9a5f9251f5cb89e857de31c8d9 (diff) | |
download | Nim-1dd9ec85b28335baae23d1e8d7bdb00a74c16cbf.tar.gz |
bugfix: typo in SMTP module; SCGI module finished
-rw-r--r-- | .gitignore | 11 | ||||
-rw-r--r-- | doc/lib.txt | 16 | ||||
-rw-r--r-- | lib/impure/ssl.nim | 4 | ||||
-rw-r--r-- | lib/pure/scgi.nim | 140 | ||||
-rw-r--r-- | lib/pure/smtp.nim | 74 | ||||
-rwxr-xr-x | todo.txt | 2 | ||||
-rw-r--r-- | web/nimrod.ini | 6 |
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" |