From fd9077056f7f33c60b218636ead0644d42e75a09 Mon Sep 17 00:00:00 2001 From: Jeffrey Armstrong Date: Sat, 27 Mar 2021 16:50:20 -0400 Subject: Minor cleanup of the template code. Started on main program handling requests. --- captain/captian.f90 | 79 ++++++++++++++++++++++++- captain/config.f90 | 106 ++++++++++++++++++++++++++++++++- captain/db.f90 | 2 +- captain/gemini.f90 | 131 +++++++++++++++++++++++++++++++++++++++++ captain/levitating-captain.prj | 36 +++++++---- captain/log.f90 | 34 +++++++++++ captain/template.f90 | 97 ++++++++++++++++++++++++------ 7 files changed, 451 insertions(+), 34 deletions(-) create mode 100644 captain/gemini.f90 create mode 100644 captain/log.f90 diff --git a/captain/captian.f90 b/captain/captian.f90 index e115280..432bb0a 100644 --- a/captain/captian.f90 +++ b/captain/captian.f90 @@ -1,12 +1,87 @@ program captain use captain_db +use config +use logging, only: initialize_log => initialize implicit none + integer::mode + integer, parameter::MODE_GEMINI = 1, & + MODE_CGI_HTML = 2 - call initialize_db("/tmp/test.db") + call random_seed() ! For possible crypto + + call parse_options() + + call initialize_db(database_filename) + call initialize_log(log_filename) - call add_player_db("windows", "asdf") + select case(mode) + case(MODE_GEMINI) + call handle_gemini() + end select call shutdown_db() + close(logunit) + +contains + + subroutine usage() + implicit none + + character(len=256)::pname + + call get_command_argument(0, pname) + + Print *, "Usage: "//trim(pname)//" " + Print *, " " + + Print *, "Options:" + Print *, " -h Display this help" + Print *, " -c Use the specified config file" + Print *, " -g Operate in Gemini mode" + Print *, " -w Operate in CGI mode (default)" + + end subroutine usage + + subroutine parse_options + use config + implicit none + + character(len=1024)::option + logical::config_loaded + integer::i + + config_loaded = .false. + mode = MODE_CGI_HTML + + i = 1 + do while(i <= command_argument_count()) + call get_command_argument(i, option) + + if(trim(option) == "-h") then + call usage() + stop + + else if(trim(option) == "-g") then + mode = MODE_GEMINI + + else if(trim(option) == "-c") then + i = i + 1 + call get_command_argument(i, option) + call load_configuration(trim(option)) + config_loaded = .true. + + end if + + i = i + 1 + end do + + ! Assign working directory from command if not specified + if(.not. config_loaded) then + Print *, "No configuration file specified" + stop + end if + + end subroutine parse_options end program captain \ No newline at end of file diff --git a/captain/config.f90 b/captain/config.f90 index 9daee6b..e66d42f 100644 --- a/captain/config.f90 +++ b/captain/config.f90 @@ -1,18 +1,120 @@ module config implicit none + character(*), parameter::TEMPLATE_DIRECTORY_VARIABLE = "template-directory" character(1024)::template_directory + + character(*), parameter::DATABASE_VARIABLE = "database" character(1024)::database_filename - character(1024)::description_filename + + character(*), parameter::LOGFILE_VARIABLE = "log-filename" + character(1024)::log_filename + + character(*), parameter::PROJECT_NAME_VARIABLE = "project" + character(32)::project + + character(*), parameter::DESCRIPTION_VARIABLE = "description" + character(1024)::description + + character(*), parameter::PUBLIC_CERT_VARIABLE = "public-cert" + character(1024)::pubcert + + character(*), parameter::PRIVATE_CERT_VARIABLE = "private-cert" + character(1024)::privcert contains + subroutine get_variable(str, v) + implicit none + + character(*), intent(in)::str + character(*), intent(out)::v + + integer::i + + v = " " + + i = index(str, '=') + if(i > 0) then + v = adjustl(str(1:i)) + end if + + end subroutine get_variable + + subroutine get_value(str, v) + implicit none + + character(*), intent(in)::str + character(*), intent(out)::v + + integer::i,n + + v = " " + + i = index(str, '=') + n = len_trim(str) + if(i > 0) then + v = adjustl(str(i+1:n)) + end if + + end subroutine get_value + + subroutine assign_config(cvariable, cvalue) + implicit none + + character(*), intent(in)::cvariable, cvalue + + if(cvariable == TEMPLATE_DIRECTORY_VARIABLE) then + template_directory = cvalue + + else if(cvariable == DATABASE_VARIABLE) then + database_filename = cvalue + + else if(cvariable == PROJECT_NAME_VARIABLE) then + project = cvalue + + else if(cvariable == DESCRIPTION_VARIABLE) then + description = cvalue + + else if(cvariable == PUBLIC_CERT_VARIABLE) then + pubcert = cvalue + + else if(cvariable == PRIVATE_CERT_VARIABLE) then + privcert = cvalue + + else if(cvariable == LOGFILE_VARIABLE) then + log_filename = cvalue + + end if + + end subroutine assign_config + subroutine load_configuration(filename) implicit none character(*), intent(in)::filename + integer::unit_number, istatus + character(1024)::line, cvalue + character(64)::cvariable - + open(newunit=unit_number, file=trim(filename), status='old', & + action="read", iostat=istatus) + + read(unit_number, '(A)', iostat=istatus) line + do while(istatus == 0) + + if(len_trim(line) > 0 .and. line(1:1) /= '#') then + + call get_variable(line, cvariable) + call get_value(line, cvalue) + call assign_config(trim(cvariable), trim(cvalue)) + + end if + + read(unit_number, '(A)', iostat=istatus) line + end do + + close(unit_number) end subroutine load_configuration diff --git a/captain/db.f90 b/captain/db.f90 index 6624cc1..5fbd0b4 100644 --- a/captain/db.f90 +++ b/captain/db.f90 @@ -37,7 +37,7 @@ contains type(sqlite3_stmt)::stmt if(stmt%prepare(db, "INSERT INTO players(name, token) VALUES(?, ?)") == SQLITE_OK) then - if(stmt%bind_text(1, "name!") == SQLITE_OK .and. stmt%bind_text(2, "token!") == SQLITE_OK) then + if(stmt%bind_text(1, name) == SQLITE_OK .and. stmt%bind_text(2, token) == SQLITE_OK) then call stmt%step_now() end if end if diff --git a/captain/gemini.f90 b/captain/gemini.f90 new file mode 100644 index 0000000..ce7b984 --- /dev/null +++ b/captain/gemini.f90 @@ -0,0 +1,131 @@ +module gemini +implicit none + +contains + + subroutine read_request(ssl, req) + use jessl, only: ssl_read + use iso_c_binding + implicit none + + type(c_ptr)::ssl + character(*), intent(out)::req + + character, dimension(64)::buf + integer::bufread + + integer::i, j + + req = " " + i = 1 + + bufread = ssl_read(ssl, buf) + do while(bufread > 0) + + do j = 1, bufread + if(buf(j) == c_new_line) then + exit + end if + + if(buf(j) /= c_carriage_return) then + req(i:i) = buf(j) + i = i + 1 + end if + + end do + + if(buf(j) == c_new_line) then + exit + end if + + bufread = ssl_read(ssl, buf) + end do + + end subroutine read_request + + subroutine handle_request() + use jessl + use iso_c_binding + use config + use iso_fortran_env + implicit none + + ! For our TLS connection + type(c_ptr)::ctx + type(c_ptr)::method + type(c_ptr)::ssl + integer(kind=c_long)::res + + ! Requested file + character(1024)::request + character(512)::mimetype + + call library_init() + + method = tls_server_method() + ctx = ctx_new(method) + + if(.not. C_ASSOCIATED(ctx)) then + call write_log("Context failed") + return + end if + + ! Seems to be a dummy now... + !res = ctx_set_ecdh_auto(ctx, 1) + + if(.not. ctx_use_certificate_file(ctx, trim(pubcert), SSL_FILETYPE_PEM)) then + call write_log("Cert file failed") + call write_log("Public: "//trim(pubcert)) + !call print_error() + return + end if + + if(.not. ctx_use_private_key_file(ctx, trim(privcert), SSL_FILETYPE_PEM)) then + call write_log("Cert file failed") + call write_log("Private: "//trim(privcert)) + !call print_error() + return + end if + + ssl = ssl_new(ctx) + + call write_log("Initiating connection") + + ! So this is a GNU Extension... + res = set_read_fd(ssl, fnum(input_unit)) + if(res /= 1) then + call write_log("set rfd failed") + !call print_error() + return + end if + + res = set_write_fd(ssl, fnum(output_unit)) + if(res /= 1) then + call write_log("set wfd failed") + !call print_error() + return + end if + + res = ssl_accept(ssl) + if(res <= 0) then + call write_log("ssl_accept failed") + !call print_error() + return + end if + + call write_log("Handling read_request") + + ! Do the actual protocol nonsense + call read_request(ssl, request) + + call write_log("Request is "//trim(request)) + + ! If it ends in a slash, let's manually and silently add "index.gmi" + if(request(len_trim(request):len_trim(request)) == "/") then + request = trim(request)//"index.gmi" + end if + + + end subroutine handle_request + +end module gemini \ No newline at end of file diff --git a/captain/levitating-captain.prj b/captain/levitating-captain.prj index 3a11a71..88c0910 100644 --- a/captain/levitating-captain.prj +++ b/captain/levitating-captain.prj @@ -4,41 +4,57 @@ "Folders":[], "Name":"+common", "Files":[{ - "filename":"../common/jessl.f90", + "filename":"..\\common\\jessl.f90", "enabled":"1" },{ - "filename":"../common/network.F90", + "filename":"..\\common\\network.F90", "enabled":"1" },{ - "filename":"../common/protocol.f90", + "filename":"..\\common\\protocol.f90", "enabled":"1" },{ - "filename":"../common/request.f90", + "filename":"..\\common\\request.f90", "enabled":"1" },{ - "filename":"../common/utilities.F90", + "filename":"..\\common\\utilities.F90", "enabled":"1" },{ - "filename":"../common/wsa.f90", + "filename":"..\\common\\wsa.f90", "enabled":"0" }] },{ "Folders":[], "Name":"+sql", "Files":[{ - "filename":"sql/create.sql", + "filename":".\\sql\\create.sql", + "enabled":"1" + }] + },{ + "Folders":[], + "Name":"+templates", + "Files":[{ + "filename":".\\templates\\index.gmi", "enabled":"1" }] }], "Name":"+levitating-captain (levitating-captain)", "Files":[{ - "filename":"captian.f90", + "filename":".\\captian.f90", + "enabled":"1" + },{ + "filename":".\\config.f90", + "enabled":"1" + },{ + "filename":".\\db.f90", + "enabled":"1" + },{ + "filename":".\\external.f90", "enabled":"1" },{ - "filename":"db.f90", + "filename":".\\sqlite.f90", "enabled":"1" },{ - "filename":"sqlite.f90", + "filename":".\\template.f90", "enabled":"1" }] }, diff --git a/captain/log.f90 b/captain/log.f90 new file mode 100644 index 0000000..3757faf --- /dev/null +++ b/captain/log.f90 @@ -0,0 +1,34 @@ +module logging +implicit none + + integer::logunit + +contains + + subroutine initialize(filename) + implicit none + + character(*), intent(in)::filename + open(newunit=logunit, file=trim(filename), action="write", status="unknown", position="append") + + end subroutine initialize + + subroutine shutdown() + implicit none + + close(logunit) + + end subroutine shutdown + + subroutine write_log(string) + implicit none + + character(*), intent(in)::string + + ! GNU Extension... :( + write(logunit, *) fdate()//" :: "//string + call flush(logunit) + + end subroutine write_log + +end module logging \ No newline at end of file diff --git a/captain/template.f90 b/captain/template.f90 index 9543c42..6b5429d 100644 --- a/captain/template.f90 +++ b/captain/template.f90 @@ -3,7 +3,8 @@ implicit none integer, parameter::VTYPE_NONE = 0, & VTYPE_STRING = 1, & - VTYPE_INTEGER = 2 + VTYPE_INTEGER = 2, & + VTYPE_LOGICAL = 3 type :: variable @@ -21,8 +22,9 @@ implicit none procedure :: set_string_value => variable_set_string_value procedure :: assign_integer => variable_assign_integer procedure :: assign_string => variable_assign_string + procedure :: assign_logical => variable_assign_logical - generic :: assign => assign_integer, assign_string + generic :: assign => assign_integer, assign_string, assign_logical end type type :: template @@ -36,12 +38,13 @@ implicit none procedure :: destroy => template_destroy procedure :: assign_integer => template_assign_integer procedure :: assign_string => template_assign_string + procedure :: assign_logical => template_assign_logical procedure :: render_unit => template_render_unit procedure :: render_filename => template_render_filename procedure :: evaluate => template_evaluate generic :: render => render_unit, render_filename - generic :: assign => assign_integer, assign_string + generic :: assign => assign_integer, assign_string, assign_logical end type contains @@ -147,6 +150,25 @@ contains call self%set_string_value(str) end subroutine variable_assign_string + + subroutine variable_assign_logical(self, name, lg) + implicit none + + class(variable)::self + character(*), intent(in)::name + logical, intent(in)::lg + + call self%set_name(name) + self%vtype = VTYPE_LOGICAL + if(lg) then + self%vint = 1 + call self%set_string_value("True") + else + self%vint = 0 + call self%set_string_value("False") + end if + + end subroutine variable_assign_logical subroutine template_init(self, filename) implicit none @@ -228,6 +250,19 @@ contains end subroutine template_assign_string + subroutine template_assign_logical(self, name, value) + implicit none + + class(template)::self + character(*), intent(in)::name + logical, intent(in)::value + integer::i + + i = template_available_variable_index(self) + call self%variables(i)%assign(name, value) + + end subroutine template_assign_logical + subroutine template_render_filename(self, filename) implicit none @@ -250,50 +285,74 @@ contains integer, intent(in)::unum character::this_char, last_char + character(len=2)::running_pair, closing_pair character(256)::brace_internals character(len=:), pointer::replacement + integer(kind=8)::input_position + + logical::writing_now + integer::input, istat, i open(newunit=input, file=self%base_filename, status="old", form="formatted", action="read") + writing_now = .true. + last_char = ' ' read(input, '(A1)', iostat=istat) this_char do while(istat == 0) - if(this_char /= '{') then - if(last_char == '{') then - write(unum, '(A1)', advance='no') last_char - end if - write(unum, '(A1)', advance='no') this_char - - ! Two curly braces - else if(last_char == '{') then + + running_pair = last_char//this_char + if(running_pair == '{{') then + + ! Future expansion + if(running_pair == '{{') then + closing_pair = '}}' + end if + brace_internals = ' ' read(input, '(A1)', iostat=istat) this_char i = 1 - do while(istat == 0 .and. .not. (this_char == '}' .and. last_char == '}')) + do while(istat == 0 .and. .not. (running_pair /= closing_pair)) brace_internals(i:i) = this_char i = i + 1 last_char = this_char read(input, '(A1)', iostat=istat) this_char + running_pair = last_char//this_char end do brace_internals(i:i) = ' ' ! Remove trailing bracket brace_internals = adjustl(brace_internals) - replacement => self%evaluate(brace_internals) + ! Simple variable + if(closing_pair == '}}') then + replacement => self%evaluate(brace_internals) + + if(associated(replacement)) then + do i=1, len_trim(replacement) + write(unum, '(A1)', advance='no') replacement(i:i) + end do + replacement => null() ! do not free - internal strings + end if + + ! Expression + else - if(associated(replacement)) then - do i=1, len_trim(replacement) - write(unum, '(A1)', advance='no') replacement(i:i) - end do - replacement => null() ! do not free - internal strings + end if + else if(last_char == '{') then + write(unum, '(A1)', advance='no') last_char + write(unum, '(A1)', advance='no') this_char + + else + write(unum, '(A1)', advance='no') this_char + end if - + last_char = this_char read(input, '(A1)', iostat=istat) this_char end do -- cgit v1.2.3