! Copyright (c) 2021 Approximatrix, LLC ! ! Permission is hereby granted, free of charge, to any person obtaining a copy ! of this software and associated documentation files (the "Software"), to deal ! in the Software without restriction, including without limitation the rights ! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ! copies of the Software, and to permit persons to whom the Software is ! furnished to do so, subject to the following conditions: ! ! The above copyright notice and this permission notice shall be included in ! all copies or substantial portions of the Software. ! ! The Software shall be used for Good, not Evil. ! ! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ! SOFTWARE. module external_handling implicit none contains function generate_jobs_gemini(req) result(res) use captain_db use server_response use request_utils, only: render_jobs_links use logging implicit none type(request)::req character(len=:), pointer::res type(job), dimension(:), pointer::jobs integer::n, nsize integer::i_start_jobs, ierr character(len=:), pointer::linklist n = get_jobs_count() if(n == 0) then allocate(character(len=1024) :: res) res = "None Yet" else if(associated(req%query_string)) then read(req%query_string, *, iostat=ierr) i_start_jobs else ierr = -1 end if if(ierr /= 0) then i_start_jobs = 1 end if jobs => get_jobs() ! 15 per page nsize = 15*(2*PLAYER_NAME_LENGTH + 64) + 1024 allocate(character(len=nsize) :: res) res = "## Jobs " linklist => render_jobs_links(jobs, i_start_jobs, min(i_start_jobs+14, n), .true.) res = trim(res)//trim(linklist) call write_log(linklist) deallocate(linklist) end if end function generate_jobs_gemini function generate_one_job_gemini(req) result(res) use captain_db use server_response use config use special_filenames, only: get_task_result_static_filename use request_utils, only: get_status_utf8, get_player_link => player_link, & get_instruction_link => instruction_link, & build_link implicit none type(request), intent(in)::req character(len=:), pointer::res character(4)::status integer::i, j, job_id type(job)::one_job character(1)::nl = new_line(' ') type(task), dimension(:), pointer::tasks character(32)::task_text, job_text, task_type character(len=:), pointer::task_results_filename character(len=:), pointer::player_link, instruction_link, result_link, one_link character(len=FILENAME_NAME_LENGTH), dimension(:), pointer::releases res => null() ! Ugh, all this nonsense call req%last_component(job_text) j = index(job_text, ".", back=.true.) job_text(j:len(job_text)) = " " read(job_text, '(I8)') job_id one_job = get_job(job_id) if(one_job%id >= 0) then allocate(character(len=1024) :: res) status = get_status_utf8(one_job%status) res = "## Status - "//status player_link => get_player_link(one_job%player, .TRUE.) if(associated(player_link)) then res = trim(res)//nl//nl//"Running on:"//nl//player_link//nl//"Last update at: "//one_job%time deallocate(player_link) end if instruction_link => get_instruction_link(one_job%instruction, .TRUE.) if(associated(instruction_link)) then res = trim(res)//nl//"Executing:"//nl//instruction_link deallocate(instruction_link) end if res = trim(res)//nl//nl//"### Task Results" tasks => get_job_tasks(job_id) if(associated(tasks)) then do i = 1, size(tasks) status = get_status_utf8(tasks(i)%status) task_results_filename => get_task_result_static_filename(one_job%id, i, no_path=.true.) write(task_text, '(I8)') i result_link => build_link("/results/"//task_results_filename, & trim(status)//" - Task "//trim(adjustl(task_text)), & .true., req%token) res = trim(res)//nl//result_link deallocate(result_link) if(get_task_type(job_id, i, task_type)) then res = trim(res)//" ("//trim(task_type)//")" end if end do deallocate(tasks) else res = trim(res)//nl//nl//"None reported yet" end if if(get_job_upload_count_by_category_db(job_id, "releases") > 0 .and. & req%auth_level >= global_permissions%get("access-releases")) & then res = trim(res)//nl//nl//"### Releases" releases => get_job_uploads_by_category_db(job_id, "releases") do i = 1, size(releases) one_link => build_link("../releases/"//trim(releases(i)), & trim(releases(i)), .true., req%token) res = trim(res)//nl//trim(one_link) end do end if else allocate(character(len=64) :: res) write(res, '(A20, 1X, I5)') "No record of:", job_id end if end function generate_one_job_gemini function generate_releases_gemini(req) result(res) use utilities use server_response use config use request_utils, only: build_link implicit none type(request), intent(in)::req character(len=:), pointer::res character(len=DIR_LIST_STRING_LENGTH), dimension(:), pointer::directories character(len=DIR_LIST_STRING_LENGTH), dimension(:), pointer::files character(1024)::public_path, local_path, subpath integer::allocation_size, i character(1)::nl = new_line(' ') character(4)::folder_icon = char(240)//char(159)//char(147)//char(129) character(len=:), pointer::release_link if(.not. associated(req%query_string)) then public_path = "/releases" local_path = release_dir else call combine_paths("/releases", req%query_string, public_path) call combine_paths(release_dir, req%query_string, local_path) end if res => null() ! Easy safety check - no relative paths if(index(local_path, '..') > 0) then allocate(character(len=64)::res) res = "None Found" return else if(req%auth_level < global_permissions%get("access-releases")) then allocate(character(len=64)::res) res = "Not Authorized" return end if directories => get_directories_in_directory(local_path) files => get_files_in_directory(local_path) allocation_size = 1024 if(associated(directories)) then allocation_size = allocation_size + size(directories) * DIR_LIST_STRING_LENGTH end if if(associated(files)) then allocation_size = allocation_size + size(files) * DIR_LIST_STRING_LENGTH end if allocate(character(len=allocation_size) :: res) res = "## Listing for "//trim(public_path) ! Add an "Up" link if(trim(public_path) /= "/releases") then i = index(req%query_string, "/", back=.true.) if(i > 0) then release_link => build_link("/releases.gmi?"//req%query_string(1:(i-1)), & " Up a directory", .true., req%token) else release_link => build_link("/releases.gmi?"//req%query_string(1:(i-1)), & " Up a directory", .true., req%token) end if res = trim(res)//nl//release_link deallocate(release_link) end if if(associated(directories)) then do i = 1, size(directories) if(associated(req%query_string)) then call combine_paths(req%query_string, trim(directories(i)), subpath) else subpath = trim(directories(i)) end if release_link => build_link("/releases.gmi?"//trim(subpath), & folder_icon//" "//trim(directories(i)), & .true., req%token) res = trim(res)//nl//release_link deallocate(release_link) end do deallocate(directories) end if if(associated(files)) then do i = 1, size(files) call combine_paths(public_path, trim(files(i)), subpath) release_link => build_link(trim(subpath), trim(files(i)), .true., req%token) res = trim(res)//nl//release_link deallocate(release_link) end do deallocate(files) end if end function generate_releases_gemini function generate_one_player_gemini(req) result(res) use captain_db use request_utils, only: get_player_status_utf8, build_link use server_response, only: request use config, only: global_permissions implicit none type(request), intent(in)::req character(len=:), pointer::res character(len=PLAYER_NAME_LENGTH)::player_name, instruction_name integer::i, pid, istat, n integer, dimension(8)::values character(len=64)::dateconvert character(len=:), pointer::one_link integer, dimension(:), pointer::instruction_ids call req%last_component(player_name) i = index(player_name, '.gmi') player_name = player_name(1:i-1) pid = get_player_id(trim(player_name)) n = get_instructions_count(player=pid) allocate(character(len=(2*n*PLAYER_NAME_LENGTH + 1024)) :: res) res = "Status:" if(is_player_online(pid)) then res = trim(res)//" Online" else res = trim(res)//" Offline" end if ! Last checkin call get_last_checkin_time(pid, values) write(dateconvert, '(I4,A1,I0.2,A1,I0.2,3X,I2,A1,I0.2,A1,I0.2)') & values(1), '-', values(2), '-', values(3), & values(5), ':', values(6), ':', values(7) res = trim(res)//new_line(' ')//'Last Checkin: '//trim(dateconvert) ! List of instructions res = trim(res)//new_line(' ')//new_line(' ')//"### Instructions for "//trim(player_name) instruction_ids => get_instruction_ids(player=pid) if(associated(instruction_ids)) then do i = 1, n call get_instruction_name(instruction_ids(i), instruction_name) one_link => build_link("../instructions/"//trim(instruction_name)//".gmi", trim(instruction_name),& .true., req%token) res = trim(res)//new_line(' ')//one_link deallocate(one_link) end do deallocate(instruction_ids) else res = trim(res)//new_line(' ')//"None Yet" end if if(req%auth_level >= global_permissions%get("modify-players")) then ! Token assignment res = trim(res)//new_line(' ')//new_line(' ')//"### Security" if(player_has_token_db(trim(player_name))) then res = trim(res)//new_line(' ')//"Player currently has a token assigned." else res = trim(res)//new_line(' ')//"Player is insecure! Please assign a token!" end if one_link => build_link(trim(player_name)//"/assign_token.gmi", "Assign Token", .true., req%token) res = trim(res)//new_line(' ')//one_link end if end function generate_one_player_gemini function generate_players_gemini(req) result(res) use captain_db use request_utils, only: get_player_status_utf8, build_link use server_response, only: request use config, only: global_permissions implicit none type(request), intent(in)::req character(len=:), pointer::res character(len=PLAYER_NAME_LENGTH), dimension(:), pointer::players character(4)::player_status integer::n, i, nsize character(len=:), pointer::player_link n = get_player_count() if(n == 0) then allocate(character(len=1024) :: res) res = "None Yet" else players => get_player_names() nsize = 1024 do i = 1, size(players) nsize = nsize + 16 + 2*len_trim(players(i)) end do allocate(character(len=nsize) :: res) res = "## Existing Players" do i = 1, n player_status = get_player_status_utf8(players(i)) player_link => build_link("/players/"//trim(players(i))//".gmi", & trim(player_status)//" "//trim(players(i)), & .true., req%token) if(i == 1) then res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//player_link else res = trim(res)//new_line(res(1:1))//player_link end if deallocate(player_link) end do deallocate(players) end if if(req%auth_level >= global_permissions%get("add-players")) then player_link => build_link("/players/add.gmi", "Add Player", .true., req%token) res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//"## Management"// & new_line(res(1:1))//player_link deallocate(player_link) end if end function generate_players_gemini function generate_one_instuction_gemini(req) result(res) use captain_db use server_response use request_utils, only: get_player_status_utf8, render_jobs_links, build_link use config, only: global_permissions implicit none type(request)::req character(len=:), pointer::res integer::id_from_req character(128)::instruction_name type(job), dimension(:), pointer::jobs integer, dimension(:), pointer::players character(len=PLAYER_NAME_LENGTH), dimension(:), pointer::all_players integer::i, j, n_jobs, n_players, nsize character(len=:), pointer::job_link_text, raw_link, launch_link, assign_link character(1)::nl = new_line(' ') character(PLAYER_NAME_LENGTH)::player_name character(4)::player_status i = index(req%location, "/", back=.true.) j = index(req%location, ".", back=.true.) instruction_name = req%location(i+1:j-1) id_from_req = get_instruction_id(trim(instruction_name)) jobs => get_jobs_for_instruction(id_from_req) if(associated(jobs)) then n_jobs = size(jobs) job_link_text => render_jobs_links(jobs, gemini_mode=.true.) else n_jobs = 0 job_link_text => null() end if players => get_instruction_players(id_from_req) if(associated(players)) then n_players = size(players) else n_players = 0 end if nsize = 1024 if(n_jobs > 0) then nsize = nsize + len_trim(job_link_text) end if if(n_players > 0) then nsize = nsize + n_players*256 end if nsize = nsize + get_player_count()*(PLAYER_NAME_LENGTH+32) allocate(character(len=nsize) :: res) res = nl//"## "//trim(instruction_name) i = index(req%location, ".gmi", back=.true.) if(req%auth_level >= global_permissions%get("view-raw-instructions")) then raw_link => build_link(req%location(1:i-1)//".json", "View Raw", .true., req%token) res = trim(res)//nl//raw_link deallocate(raw_link) end if if(n_players == 0) then res = trim(res)//nl//nl//"No players currently can run these instructions" else if(req%auth_level >= global_permissions%get("launch-job")) then res = trim(res)//nl//nl//"### Launch Now" do i = 1, n_players call get_player_name(players(i), player_name) player_status = get_player_status_utf8(players(i)) launch_link => build_link(trim(req%location)//"?launch="//trim(player_name), & trim(player_status)//" "//trim(player_name), & .true., req%token) res = trim(res)//nl//launch_link deallocate(launch_link) end do else res = trim(res)//nl//nl//"### Assigned Players" do i = 1, n_players call get_player_name(players(i), player_name) player_status = get_player_status_utf8(players(i)) launch_link => build_link("/players/"//trim(player_name)//".gmi", & trim(player_status)//" "//trim(player_name), & .true., req%token) res = trim(res)//nl//launch_link deallocate(launch_link) end do end if res = trim(res)//nl//nl//"### Jobs" if(n_jobs == 0) then res = trim(res)//nl//"None Yet" else res = trim(res)//nl//job_link_text deallocate(job_link_text) end if if(req%auth_level >= global_permissions%get("assign-instructions")) then all_players => get_player_names() if(associated(all_players)) then res = trim(res)//nl//nl//"### Assign"//nl//"Assign a player to these instructions" do i = 1, size(all_players) if(n_players > 0) then j = get_player_id(all_players(i)) if(any(j == players)) then cycle end if end if assign_link => build_link(trim(req%location)//"?assign="//trim(all_players(i)), & trim(all_players(i)), .true., req%token) res = trim(res)//nl//assign_link deallocate(assign_link) end do deallocate(all_players) end if if(n_players > 0) then res = trim(res)//nl//nl//"### Remove"//nl//"Remove a player from these instructions" do i = 1, n_players call get_player_name(players(i), player_name) assign_link => build_link(trim(req%location)//"?remove="//trim(player_name), & trim(player_name), .true., req%token) res = trim(res)//nl//assign_link deallocate(assign_link) end do end if end if end function generate_one_instuction_gemini function generate_instructions_gemini(req) result(res) use captain_db use server_response, only: request use request_utils, only: build_link use config, only: global_permissions implicit none class(request), intent(in)::req character(len=:), pointer::res character(len=PLAYER_NAME_LENGTH), dimension(:), pointer::instruction_names integer::n, i, nsize character(len=3*PLAYER_NAME_LENGTH)::one_player character(len=:), pointer::instruction_link n = get_instructions_count() if(n == 0) then allocate(character(len=1024) :: res) res = "None Yet" else instruction_names => get_instruction_names() nsize = 1024 do i = 1, size(instruction_names) nsize = nsize + 16 + 2*len_trim(instruction_names(i)) end do allocate(character(len=nsize) :: res) res = "## Instructions" do i = 1, n instruction_link => build_link("/instructions/"//trim(instruction_names(i))//".gmi", & trim(instruction_names(i)), .true., req%token) if(i == 1) then res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//instruction_link else res = trim(res)//new_line(res(1:1))//instruction_link end if deallocate(instruction_link) end do deallocate(instruction_names) end if if(req%auth_level >= global_permissions%get("scan-instructions")) then instruction_link => build_link("/instructions/scan.gmi", "Scan for Instructions", & .true., req%token) res = trim(res)//new_line(res(1:1))//new_line(res(1:1))//"## Management"// & new_line(res(1:1))//instruction_link deallocate(instruction_link) end if end function generate_instructions_gemini function generate_profile_gemini(req) result(res) use server_response, only: request use request_utils, only: build_link use captain_db implicit none class(request), intent(in)::req character(len=:), pointer::res character(len=:), pointer::link character(len=128)::username if(.not. associated(req%token)) then allocate(character(len=128)::res) res = "Not currently logged in."//new_line(' ')//"=> /login.gmi Login" return else if(.not. is_valid_session_db(req%token)) then call destroy_session_db(req%token) allocate(character(len=128)::res) res = "You need to login again."//new_line(' ')//"=> /login.gmi Login" return end if allocate(character(len=1024)::res) call get_session_username_db(req%token, username) res = "## "//trim(username)//" Profile" link => build_link("/logout.gmi", "Logout", .true., req%token) res = trim(res)//new_line(' ')//link deallocate(link) res = trim(res)//new_line(' ')//new_line(' ')//"More to come soon!" end function generate_profile_gemini pure function is_input_provided_request(req) use server_response, only: request implicit none class(request), intent(in)::req logical::is_input_provided_request is_input_provided_request = associated(req%query_string) end function is_input_provided_request function is_input_required_request(req) use server_response, only: request implicit none class(request), intent(in)::req logical::is_input_required_request character(64)::first, last call req%path_component(1, first) call req%last_component(last) is_input_required_request = .false. if(req%location == "/players/add.gmi" .or. & req%location == "/login.gmi" .or. & trim(first) == "login" .or. & trim(last) == "assign_token.gmi") & then is_input_required_request = .true. end if end function is_input_required_request function external_input_required_gemini(req) result(resp) use server_response use gemini_codes use config, only: global_permissions implicit none class(request), intent(in)::req type(response)::resp character(64)::first, last call req%path_component(1, first) call req%last_component(last) resp%code = GEMINI_CODE_INPUT if(req%location == "/players/add.gmi" .and. req%auth_level >= global_permissions%get("add-players")) then call resp%set_message("Enter name of new player to add") else if(req%location == "/login.gmi") then call resp%set_message("Enter username:") else if(trim(first) == "login") then call resp%set_message("Enter password:") resp%code = GEMINI_CODE_INPUT_PW else if(trim(last) == "assign_token.gmi") then call resp%set_message("Enter token for player:") end if end function external_input_required_gemini function external_input_request_gemini(req) result(resp) use server_response use captain_db use request_utils, only: handle_instruction_command use m_uuid, only: UUID_LENGTH use logging use gemini_codes use config, only: global_permissions implicit none class(request), intent(in)::req type(response)::resp character(PLAYER_NAME_LENGTH)::first, second, last character(len=:), pointer::session call req%path_component(1, first) call req%last_component(last) if(req%location == "/players/add.gmi" .and. req%auth_level >= global_permissions%get("add-players")) then call add_player_db(req%query_string) resp%code = GEMINI_CODE_REDIRECT call resp%set_gemini_session_url("/players.gmi", req%token) else if(req%location == "/jobs.gmi" .or. req%location == "/releases.gmi") then ! Used for paging (jobs) or subdirs (releases) - send it back resp = external_request_templated(req) else if(trim(first) == "instructions") then ! Instruction command call handle_instruction_command(req) ! Go back to the same location resp%code = GEMINI_CODE_REDIRECT call resp%set_url(req%location) else if(req%location == "/login.gmi" .and. req%has_query()) then resp%code = GEMINI_CODE_REDIRECT call resp%set_url("/login/"//req%query_string//"/password.gmi") else if(trim(first) == "login") then call req%path_component(2, second) resp%code = GEMINI_CODE_REDIRECT call write_log("Attempting to validate "//trim(second)//":"//trim(req%query_string), LOG_DEBUG) if(validate_user_db(trim(second), trim(req%query_string))) then allocate(character(len=UUID_LENGTH)::session) session = create_user_session_db(trim(second)) call resp%set_gemini_session_url("/index.gmi", session) deallocate(session) else call resp%set_url("/loginfailed.gmi") end if else if(trim(last) == "assign_token.gmi" .and. req%auth_level >= global_permissions%get("modify-players")) then call req%path_component(2, second) call update_player_token_db(trim(second), req%query_string) resp%code = GEMINI_CODE_REDIRECT call resp%set_url("../"//trim(second)//".gmi") end if end function external_input_request_gemini function is_redirect_action(req) use server_response implicit none class(request), intent(in)::req logical::is_redirect_action is_redirect_action = .false. if(req%location == "instructions/scan.gmi" .or. & req%location == "/logout.gmi") & then is_redirect_action = .true. end if end function is_redirect_action subroutine handle_basic_gemini_template_components(req, page) use server_response use page_template use captain_db use config implicit none type(request), intent(in)::req type(template), intent(inout)::page character(len=128)::username call page%assign('project', project) if(req%is_authenticated_user()) then call page%assign("session_flag", "/session-"//req%token) call page%assign('user_link_page', "/profile.gmi") call get_session_username_db(req%token, username) call page%assign('user_link_text', achar(240)//achar(159)//achar(152)//achar(128)//" "//trim(username)) else call page%assign("session_flag", "") call page%assign('user_link_page', "/login.gmi") call page%assign('user_link_text', "Login") end if end subroutine handle_basic_gemini_template_components function external_redirect_action_request_gemini(req) result(resp) use captain_db use server_response use logging use gemini_codes use config, only: global_permissions implicit none class(request), intent(inout)::req type(response)::resp resp%code = GEMINI_CODE_REDIRECT if(req%location == "/instructions/scan.gmi" .and. req%auth_level >= global_permissions%get("scan-instructions")) then call scan_instructions_for_db() call resp%set_url("/instructions.gmi") else if(req%location == "/logout.gmi") then if(associated(req%token)) then call destroy_session_db(req%token) call req%clear_token() end if call resp%set_url("/index.gmi") end if end function external_redirect_action_request_gemini function external_request_templated(req) result(resp) use page_template use config, only: template_filepath, project use logging use server_response use request_utils, only: get_job_page_title use utilities, only: build_date use gemini_codes use captain_db, only: PLAYER_NAME_LENGTH implicit none class(request), intent(in)::req type(response)::resp character(1024)::template_file type(template)::page character(len=:), pointer::contents character(64)::first, template_name character(128)::job_page_title character(PLAYER_NAME_LENGTH)::second integer::i ! Open the base template if(trim(req%location) == "/" .or. trim(req%location) == "/index.gmi") then template_name = "home.gmi" else if(trim(req%location) == "/about.gmi") then template_name = "about.gmi" else template_name = "index.gmi" end if call template_filepath(template_name, template_file) call page%init(trim(template_file)) call write_log("Processing request", LOG_INFO) call req%path_component(1, first) if(trim(req%location) == "/" .or. trim(req%location) == "/index.gmi") then call page%assign('title', 'Home') else if(trim(req%location) == "/releases.gmi") then call page%assign('title', 'Releases') contents => generate_releases_gemini(req) call page%assign('contents', contents) else if(trim(req%location) == "/jobs.gmi") then call page%assign('title', 'Jobs') contents => generate_jobs_gemini(req) call page%assign('contents', contents) else if(trim(req%location) == "/players.gmi") then call page%assign('title', 'Players') contents => generate_players_gemini(req) call page%assign('contents', contents) else if(trim(first) == 'players') then call req%path_component(2, second) i = index(second, ".gmi") if(i > 2) then call page%assign('title', second(1:i-1)) end if contents => generate_one_player_gemini(req) call page%assign('contents', contents) else if(trim(req%location) == "/about.gmi") then call page%assign('title', 'About') call page%assign('build_date', build_date()) else if(trim(req%location) == "/instructions.gmi") then call page%assign('title', 'Build Instructions') contents => generate_instructions_gemini(req) call page%assign('contents', contents) else if(trim(first) == "instructions") then call page%assign('title', 'Build Instructions') contents => generate_one_instuction_gemini(req) call page%assign('contents', contents) else if(trim(first) == "jobs") then call get_job_page_title(req, job_page_title) call page%assign('title', trim(job_page_title)) contents => generate_one_job_gemini(req) call page%assign('contents', contents) else if(req%location == "/profile.gmi") then call page%assign('title', "User Profile") contents => generate_profile_gemini(req) call page%assign('contents', contents) else if(req%location == "/loginfailed.gmi") then call page%assign('title', "Login Failed") call page%assign("contents", "Bad username and/or password.") else call page%assign('title', 'Not Found') end if call handle_basic_gemini_template_components(req, page) call write_log("Rendering page for "//req%location) call page%render() call write_log("Finalizing response", LOG_INFO) resp%temporary_file = .true. resp%body_filename => page%output_filename resp%body_mimetype = "text/gemini" resp%code = GEMINI_CODE_SUCCESS end function external_request_templated function external_request_gemini(req) result(resp) use server_response use logging use request_utils, only: is_request_static, request_static implicit none class(request), intent(inout)::req ! inout for logout actions, unfortunately... type(response)::resp if(is_redirect_action(req)) then call write_log("Action request", LOG_INFO) resp = external_redirect_action_request_gemini(req) else if(is_input_provided_request(req)) then call write_log("Input request", LOG_INFO) resp = external_input_request_gemini(req) else if(is_input_required_request(req)) then call write_log("Input required", LOG_INFO) resp = external_input_required_gemini(req) else if(is_request_static(req)) then call write_log("Req static", LOG_INFO) resp = request_static(req) else call write_log("Req template", LOG_INFO) resp = external_request_templated(req) end if end function external_request_gemini function external_request_titan(req) result(resp) use server_response use special_filenames use query_utilities use security use logging use gemini_codes implicit none type(titan_request), intent(in)::req type(response)::resp type(query)::q character(len=:), pointer::fullpath character(12)::job_text, task_text integer::job_id, task_num logical::proceed_to_create_filename character(64)::msg fullpath => null() proceed_to_create_filename = .false. call write_log("Validating token...") proceed_to_create_filename = validate_titan_token(req%token) call write_log("Proceeding...") if(proceed_to_create_filename) then fullpath => get_full_filename_from_request(req) end if if(associated(fullpath)) then ! Write the file call write_log("Storing titan file to "//trim(fullpath), LOG_DEBUG) if(req%write_to(fullpath)) then resp%code = GEMINI_CODE_SUCCESS call resp%set_body_contents(RESPONSE_JSON_OKAY) resp%body_mimetype = "text/plain" if(req%q%has_key("job")) then call write_log("Ok, log") call report_upload(req) call write_log("Upload logged?") end if else resp%code = GEMINI_CODE_TEMPFAIL end if else resp%code = GEMINI_CODE_PERMFAIL end if end function external_request_titan end module external_handling