Skip to content

Commit 43e10ab

Browse files
committed
initial commit
0 parents  commit 43e10ab

8 files changed

+365
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.stack-work/
2+
TAGS
3+
fingerd.db

LICENSE

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Copyright Michael Angelo Calimlim (c) 2019
2+
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
11+
* Redistributions in binary form must reproduce the above
12+
copyright notice, this list of conditions and the following
13+
disclaimer in the documentation and/or other materials provided
14+
with the distribution.
15+
16+
* Neither the name of Michael Angelo Calimlim nor the names of other
17+
contributors may be used to endorse or promote products derived
18+
from this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# fingerd
2+
3+
The final project of `Haskell Programming from First Principles` book by Chris Allen and Julie Moronuki

Setup.hs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain

fingerd.cabal

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: fingerd
2+
version: 0.1.0.0
3+
synopsis: Simple project template
4+
description: Please see README.md
5+
homepage: https://github.com/macalimlim/fingerd#readme
6+
license: BSD3
7+
license-file: LICENSE
8+
author: Michael Angelo Calimlim
9+
maintainer: [email protected]
10+
copyright: 2019 Michael Angelo Calimlim
11+
category: Web
12+
build-type: Simple
13+
cabal-version: >=1.10
14+
extra-source-files: README.md
15+
16+
executable debug
17+
ghc-options: -Wall
18+
hs-source-dirs: src
19+
main-is: Debug.hs
20+
default-language: Haskell2010
21+
build-depends: base >= 4.7 && < 5
22+
, network
23+
24+
executable fingerd
25+
ghc-options: -Wall
26+
hs-source-dirs: src
27+
main-is: Main.hs
28+
default-language: Haskell2010
29+
build-depends: base >= 4.7 && < 5
30+
, bytestring
31+
, integer-gmp
32+
, network
33+
, raw-strings-qq
34+
, sqlite-simple
35+
, text

src/Debug.hs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{-# LANGUAGE NoImplicitPrelude #-}
2+
3+
module Main where
4+
5+
import Control.Monad (forever)
6+
import Data.Function (($))
7+
import Data.List (head)
8+
import Data.Maybe (Maybe (Just, Nothing))
9+
import Network.Socket (AddrInfoFlag (AI_PASSIVE), Socket,
10+
SocketType (Stream), accept, addrAddress,
11+
addrFamily, addrFlags, bind, close,
12+
defaultHints, defaultProtocol, getAddrInfo,
13+
listen, socket, withSocketsDo)
14+
import Network.Socket.ByteString (recv, sendAll)
15+
import System.IO (IO, print)
16+
17+
logAndEcho :: Socket -> IO ()
18+
logAndEcho sock = forever $ do
19+
(s, _) <- accept sock
20+
printAndKickback s
21+
close s
22+
where printAndKickback conn = do
23+
msg <- recv conn 1024
24+
print msg
25+
sendAll conn msg
26+
27+
main :: IO ()
28+
main = withSocketsDo $ do
29+
addrInfos <- getAddrInfo (Just (defaultHints {addrFlags = [AI_PASSIVE]})) Nothing (Just "79")
30+
let serverAddr = head addrInfos
31+
sock <- socket (addrFamily serverAddr) Stream defaultProtocol
32+
bind sock (addrAddress serverAddr)
33+
listen sock 1
34+
logAndEcho sock
35+
close sock

src/Main.hs

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
{-# LANGUAGE NoImplicitPrelude #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE QuasiQuotes #-}
4+
{-# LANGUAGE RecordWildCards #-}
5+
6+
module Main where
7+
8+
import Control.Applicative ((<*>))
9+
import Control.Exception (Exception, throwIO)
10+
import Control.Monad (forever, mapM_, return)
11+
import Data.ByteString (ByteString, concat)
12+
import Data.Eq (Eq)
13+
import Data.Function (($))
14+
import Data.Functor ((<$>))
15+
import Data.List (head, map, (++))
16+
import Data.Maybe (Maybe (Just, Nothing))
17+
import Data.Text (Text, intercalate, strip)
18+
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
19+
import Data.Typeable (Typeable)
20+
import Database.SQLite.Simple.FromRow (FromRow (fromRow))
21+
import Database.SQLite.Simple.ToRow (ToRow (toRow))
22+
import Database.SQLite.Simple.Types
23+
import GHC.Integer (Integer)
24+
import System.IO (IO, print, putStrLn)
25+
import Text.RawString.QQ (r)
26+
import Text.Show (Show, show)
27+
28+
import Database.SQLite.Simple as DB
29+
import Network.Socket as Sock
30+
import Network.Socket.ByteString as SockBS
31+
32+
type UserName = Text
33+
type Shell = Text
34+
type HomeDirectory = Text
35+
type RealName = Text
36+
type Phone = Text
37+
38+
type UserRow = ( Null
39+
, UserName
40+
, Shell
41+
, HomeDirectory
42+
, RealName
43+
, Phone
44+
)
45+
46+
data User = User
47+
{ userId :: Integer
48+
, userName :: UserName
49+
, shell :: Shell
50+
, homeDirectory :: HomeDirectory
51+
, realName :: RealName
52+
, phone :: Phone
53+
} deriving (Eq, Show)
54+
55+
instance FromRow User where
56+
fromRow = User <$> field
57+
<*> field
58+
<*> field
59+
<*> field
60+
<*> field
61+
<*> field
62+
63+
instance ToRow User where
64+
toRow User{..} = toRow ( userId
65+
, userName
66+
, shell
67+
, homeDirectory
68+
, realName
69+
, phone
70+
)
71+
72+
data DuplicateDataEx = DuplicateDataEx
73+
deriving (Eq, Show, Typeable)
74+
75+
instance Exception DuplicateDataEx
76+
77+
createUsersTable :: Query
78+
createUsersTable = [r|
79+
CREATE TABLE IF NOT EXISTS users
80+
( id INTEGER PRIMARY KEY AUTOINCREMENT
81+
, username TEXT UNIQUE
82+
, shell TEXT
83+
, homedirectory TEXT
84+
, realname TEXT
85+
, phone TEXT
86+
)
87+
|]
88+
89+
insertUserQuery :: Query
90+
insertUserQuery = "INSERT INTO users VALUES (?, ?, ?, ?, ?, ?)"
91+
92+
allUsersQuery :: Query
93+
allUsersQuery = "SELECT * FROM users"
94+
95+
getUserByUserNameQuery :: Query
96+
getUserByUserNameQuery = "SELECT * FROM users WHERE username = ?"
97+
98+
getUser :: Connection -> UserName -> IO (Maybe User)
99+
getUser conn un = do
100+
results <- query conn getUserByUserNameQuery (Only un)
101+
case results of
102+
[] -> return Nothing
103+
[user] -> return $ Just user
104+
_ -> throwIO DuplicateDataEx
105+
106+
createDatabase :: IO ()
107+
createDatabase = do
108+
conn <- open "fingerd.db"
109+
execute_ conn createUsersTable
110+
execute conn insertUserQuery userRow
111+
rows <- query_ conn allUsersQuery
112+
mapM_ print (rows :: [User])
113+
DB.close conn
114+
where userRow :: UserRow
115+
userRow = ( Null
116+
, "macalimlim"
117+
, "/run/current-system/sw/bin/zsh"
118+
, "/home/macalimlim"
119+
, "Michael Angelo Calimlim"
120+
, "1234567890"
121+
)
122+
123+
returnUsers :: Connection -> Socket -> IO ()
124+
returnUsers conn sock = do
125+
rows <- query_ conn allUsersQuery
126+
let userNames = map userName rows
127+
newlineSeparated = intercalate "\n" userNames
128+
sendAll sock $ encodeUtf8 newlineSeparated
129+
130+
formatUser :: User -> ByteString
131+
formatUser User{..} = concat [ "Login: ", encodeUtf8 userName, "\t\t\t\t"
132+
, "Name: ", encodeUtf8 realName, "\n"
133+
, "Directory: ", encodeUtf8 homeDirectory, "\t\t\t"
134+
, "Shell: ", encodeUtf8 shell, "\n"
135+
]
136+
137+
returnUser :: Connection -> Socket -> UserName -> IO ()
138+
returnUser conn sock un = do
139+
maybeUser <- getUser conn $ strip un
140+
case maybeUser of
141+
Nothing -> putStrLn ("Cannot find user: " ++ show un)
142+
Just user -> sendAll sock $ formatUser user
143+
144+
handleQuery :: Connection -> Socket -> IO ()
145+
handleQuery conn sock = do
146+
msg <- SockBS.recv sock 1024
147+
case msg of
148+
"\r\n" -> returnUsers conn sock
149+
name -> returnUser conn sock $ decodeUtf8 name
150+
151+
handleQueries :: Connection -> Socket -> IO ()
152+
handleQueries conn sock = forever $ do
153+
(s, _) <- accept sock
154+
putStrLn "Got connection, handling query"
155+
handleQuery conn s
156+
Sock.close s
157+
158+
main :: IO ()
159+
main = withSocketsDo $ do
160+
addrInfos <- getAddrInfo (Just (defaultHints {addrFlags = [AI_PASSIVE]})) Nothing (Just "79")
161+
let serverAddr = head addrInfos
162+
sock <- socket (addrFamily serverAddr) Stream defaultProtocol
163+
Sock.bind sock (addrAddress serverAddr)
164+
listen sock 1
165+
conn <- open "fingerd.db"
166+
handleQueries conn sock
167+
DB.close conn
168+
Sock.close sock
169+
170+
{-
171+
172+
Chapter Exercises
173+
174+
1. Try using the sqlite3 command line interface to add a new user
175+
or modify an existing user in finger.db.
176+
177+
2. Write an executable separate of fingerd and debug which allows
178+
you to add new users to the database.
179+
180+
3. Add the ability to modify an existing user in the database.
181+
182+
4. Bound on a different port, try creating a “control socket” that
183+
permits inserting new data into the database while the server is
184+
running. This will probably require, at minimum, learning how
185+
to use forkIO and the basics of concurrency in Haskell among
186+
other things. Design the format for representing the user rows
187+
passed over the TCP socket yourself. For bonus points, write
188+
your own client executable that takes the arguments from the
189+
command line as well.
190+
191+
5. Celebrate completing this massive book.
192+
193+
-}

stack.yaml

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# This file was automatically generated by 'stack init'
2+
#
3+
# Some commonly used options have been documented as comments in this file.
4+
# For advanced use and comprehensive documentation of the format, please see:
5+
# https://docs.haskellstack.org/en/stable/yaml_configuration/
6+
7+
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
8+
# A snapshot resolver dictates the compiler version and the set of packages
9+
# to be used for project dependencies. For example:
10+
#
11+
# resolver: lts-3.5
12+
# resolver: nightly-2015-09-21
13+
# resolver: ghc-7.10.2
14+
#
15+
# The location of a snapshot can be provided as a file or url. Stack assumes
16+
# a snapshot provided as a file might change, whereas a url resource does not.
17+
#
18+
# resolver: ./custom-snapshot.yaml
19+
# resolver: https://example.com/snapshots/2018-01-01.yaml
20+
resolver: lts-13.17
21+
22+
# User packages to be built.
23+
# Various formats can be used as shown in the example below.
24+
#
25+
# packages:
26+
# - some-directory
27+
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
28+
# - location:
29+
# git: https://github.com/commercialhaskell/stack.git
30+
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
31+
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
32+
# subdirs:
33+
# - auto-update
34+
# - wai
35+
packages:
36+
- .
37+
# Dependency packages to be pulled from upstream that are not in the resolver
38+
# using the same syntax as the packages field.
39+
# (e.g., acme-missiles-0.3)
40+
# extra-deps: []
41+
42+
# Override default flag values for local packages and extra-deps
43+
# flags: {}
44+
45+
# Extra package databases containing global packages
46+
# extra-package-dbs: []
47+
48+
# Control whether we use the GHC we find on the path
49+
# system-ghc: true
50+
#
51+
# Require a specific version of stack, using version ranges
52+
# require-stack-version: -any # Default
53+
# require-stack-version: ">=1.9"
54+
#
55+
# Override the architecture used by stack, especially useful on Windows
56+
# arch: i386
57+
# arch: x86_64
58+
#
59+
# Extra directories used by stack for building
60+
# extra-include-dirs: [/path/to/dir]
61+
# extra-lib-dirs: [/path/to/dir]
62+
#
63+
# Allow a newer minor version of GHC than the snapshot specifies
64+
# compiler-check: newer-minor

0 commit comments

Comments
 (0)