From f32d7d30c9cd38544349697e475355e8a2e2a478 Mon Sep 17 00:00:00 2001 From: Jeffrey Armstrong Date: Thu, 21 May 2020 12:25:14 -0400 Subject: Binary files are now actually handled in the dumb terminal version. --- binary.f90 | 63 +++++++++++++++++++++++++ dumb_binary.f90 | 92 ++++++++++++++++++++++++++++++++++++ gemini.prj | 6 +++ main.F90 | 6 ++- protocol.f90 | 144 ++++++++++++++++++++++++++++++++++++++++++++------------ render.f90 | 11 +++++ 6 files changed, 289 insertions(+), 33 deletions(-) create mode 100644 binary.f90 create mode 100644 dumb_binary.f90 diff --git a/binary.f90 b/binary.f90 new file mode 100644 index 0000000..3cd58c6 --- /dev/null +++ b/binary.f90 @@ -0,0 +1,63 @@ +! Copyright (c) 2020 Jeffrey Armstrong +! +! 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 binary +implicit none + + integer, parameter::binary_okay = 0 + integer, parameter::binary_ignore = -1 + integer, parameter::binary_error = -2 + + type, abstract :: binary_handler + + contains + + ! Should return a stream-opened unit number + procedure(handle_binary), deferred::handle_binary + + end type + + abstract interface + function handle_binary(self, mimetype, url, iostatus) + import::binary_handler + class(binary_handler)::self + character(*), intent(in)::mimetype + character(*), intent(in)::url + integer, intent(out)::iostatus + integer::handle_binary + end function handle_binary + end interface + + contains + + function is_binary_file(mimetype) result(res) + implicit none + + character(*), intent(in)::mimetype + logical::res + + ! So I guess this actually works... + res = (index(mimetype, "text/") <= 0) + + end function is_binary_file + +end module binary diff --git a/dumb_binary.f90 b/dumb_binary.f90 new file mode 100644 index 0000000..f12bb66 --- /dev/null +++ b/dumb_binary.f90 @@ -0,0 +1,92 @@ +! Copyright (c) 2020 Jeffrey Armstrong +! +! 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 dumb_binary +use binary +implicit none + + type, extends(binary_handler) :: dumb_binary_handler + + contains + + procedure :: handle_binary => dumb_handle_binary + + end type + +contains + + function dumb_handle_binary(self, mimetype, url, iostatus) result(unit_number) + + class(dumb_binary_handler)::self + character(*), intent(in)::mimetype + character(*), intent(in)::url + integer, intent(out)::iostatus + integer::unit_number + + character::asksave + character(256)::filename + character(256)::guessed_filename + integer::i, istatus + + iostatus = binary_ignore + + write(*,'(1X, A44)', advance='no') "*** Binary file encountered, Save? (Y/N) => " + + read(*, '(A1)') asksave + + if(asksave == 'y' .or. asksave == 'Y') then + + filename = ' ' + i = index(url, '/', back=.true.) + if(i > 0) then + + guessed_filename = url(i+1:len_trim(url)) + + else + + guessed_filename = "file.bin" + + end if + + write(*, *) "*** Enter filename to save ["//trim(guessed_filename)//"]" + write(*, '(1X, A3)', advance='no') "=> " + read(*, '(a)', iostat=istatus) filename + + if(len_trim(filename) == 0 .or. istatus /= 0) then + filename = guessed_filename + end if + + unit_number = 0 + + open(newunit=unit_number, file=trim(filename), status='UNKNOWN', & + access='STREAM', form='FORMATTED', iostat=istatus) + if(istatus /= 0) then + iostatus = binary_error + else + iostatus = binary_okay + end if + + end if + + end function dumb_handle_binary + +end module dumb_binary diff --git a/gemini.prj b/gemini.prj index a83bd3a..f61f32e 100644 --- a/gemini.prj +++ b/gemini.prj @@ -23,6 +23,12 @@ }], "Name":"+gemini (lr87)", "Files":[{ + "filename":"binary.f90", + "enabled":"1" + },{ + "filename":"dumb_binary.f90", + "enabled":"1" + },{ "filename":"dumb_render.f90", "enabled":"1" },{ diff --git a/main.F90 b/main.F90 index 6d6b78d..af4a29d 100644 --- a/main.F90 +++ b/main.F90 @@ -27,6 +27,7 @@ use request use ag_render, only: appgraphics_renderer #else use dumb_render +use dumb_binary #endif use render @@ -50,6 +51,7 @@ implicit none type(appgraphics_renderer)::r #else type(dumb_renderer)::r + type(dumb_binary_handler)::bh #endif logical::running @@ -103,7 +105,7 @@ implicit none do while(running) if(index(current_url, "gemini://") /= 1) then - call r%draw_error("Only gemini:// URLs supported ("//trim(current_url)//")") + call r%report_unsupported_protocol(trim(current_url)) populated = .false. loaded = .true. return_code = STATUS_LOCALFAIL @@ -113,7 +115,7 @@ implicit none call r%report_status("Requesting "//trim(current_url)) - return_code = request_url(current_url, io, return_type) + return_code = request_url(current_url, io, return_type, bh) populated = .true. call update_status(r, current_url, return_code) diff --git a/protocol.f90 b/protocol.f90 index 78f11c8..881314b 100644 --- a/protocol.f90 +++ b/protocol.f90 @@ -37,53 +37,66 @@ implicit none integer, parameter::gemini_default_port = 1965 contains - - subroutine read_mimetype(unit_number, return_type) + + subroutine get_mimetype(status_line, return_type) implicit none - integer, intent(in)::unit_number + + character(*), intent(in)::status_line character(*), intent(out)::return_type - character::c - integer::iostatus, i + integer::i, j return_type = " " ! Advance past status and space - read(unit_number, '(A1)', advance='no', iostat=iostatus) c - do while(c /= " " .and. c /= char(9) .and. iostatus == 0) - read(unit_number, '(A1)', advance='no', iostat=iostatus) c + i = 1 + do while(status_line(i:i) /= " " .and. status_line(i:i) /= char(9) .and. i < len_trim(status_line)) + i = i + 1 end do - do while((c == " " .or. c == char(9)) .and. iostatus == 0) - read(unit_number, '(A1)', advance='no', iostat=iostatus) c + do while((status_line(i:i) == " " .or. status_line(i:i) == char(9)) .and. i < len_trim(status_line)) + i = i + 1 + end do + + j = 0 + do while(.not. any([char(13), char(10)] == status_line(i:i))) + j = j + 1 + return_type(j:j) = status_line(i:i) + i = i + 1 end do - if(iostatus == 0) then - ! c now contains the first entry of the mimetype - i = 0 - do while(iostatus == 0) - i = i + 1 - return_type(i:i) = c - read(unit_number, '(A1)', advance='no', iostat=iostatus) c - end do - - end if - ! Default if(len_trim(return_type) == 0) then return_type = "text/gemini" end if - end subroutine read_mimetype + end subroutine get_mimetype + + function get_return_code(status_line) result(ret) + implicit none + + integer::ret + character(*), intent(in)::status_line + integer::istatus + + read(status_line, '(i1)', iostat=istatus) ret + + if(istatus /= 0) then + ret = STATUS_BADRESPONSE + end if + + end function get_return_code - function request_url(url, unit_number, return_type, server_name) result(returncode) + function request_url(url, unit_number, return_type, bh, server_name) result(returncode) use request use iso_c_binding use file_handling, only: mark_file_end + use binary, only: binary_handler, is_binary_file, binary_okay implicit none character(*), intent(inout)::url integer, intent(in)::unit_number character(*), intent(out)::return_type + class(binary_handler)::bh character(*), intent(in), optional::server_name integer::port @@ -93,9 +106,18 @@ contains type(connection)::conn - integer::bytes_received, i, iostatus + integer::bytes_received, i character, dimension(BUFFER_SIZE)::buffer + ! In case we encounter a mime type we don't handle, we'll try to save it... + integer::binary_unit, binary_status + logical::binary_file + + ! For direct processing of the reponse line + integer::response_line_index + character(1024)::response_line + logical::response_line_completed + returncode = -1 rewind(unit_number) @@ -118,25 +140,85 @@ contains if(conn%code == CONNECTION_OPEN) then if(send_string(conn%ssl, trim(url)//c_carriage_return//c_new_line, trimming=.false.)) then + response_line_completed = .false. + response_line = " " + response_line_index = 0 + binary_unit = 0 + binary_file = .false. + bytes_received = retrieve_characters(conn%ssl, buffer) do while(bytes_received > 0) do i=1, bytes_received - write(unit_number, '(A1)', advance='no') buffer(i) + + if(.not. response_line_completed) then + response_line_index = response_line_index + 1 + response_line(response_line_index:response_line_index) = buffer(i) + + ! If we encountered our first newline, we have a complete status + ! line - handle it here + if(buffer(i) == char(10) .or. response_line_index == 1024) then + response_line_completed = .true. + + returncode = get_return_code(response_line) + + if(returncode == STATUS_SUCCESS) then + + call get_mimetype(response_line, return_type) + binary_file = is_binary_file(return_type) + + if(binary_file) then + binary_unit = bh%handle_binary(return_type, trim(url), binary_status) + end if + + else + + ! If it's not success, the rest of the code needs this line + ! To avoid spaces, we'll do this manually (rather than construct + ! a Fortran format string) + do response_line_index = 1, len_trim(response_line) + write(unit_number, '(A1)', advance='no') & + response_line(response_line_index:response_line_index) + end do + write(unit_number, '(A2)', advance='no') char(13)//char(10) + + end if + + end if + + else if(binary_file .and. binary_status == binary_okay) then + + write(binary_unit, '(A1)', advance='no') buffer(i) + + else if(.not. binary_file) then + + write(unit_number, '(A1)', advance='no') buffer(i) + + end if + end do bytes_received = retrieve_characters(conn%ssl, buffer) end do + ! Done with bytes. If a binary file... + if(binary_file) then + + if(binary_status == binary_okay) then + close(binary_unit) + write(unit_number, *) "Binary file completed" + else + write(unit_number, *) "Binary file ignored" + end if + + ! For now, just reset to gemini + return_type = "text/gemini" + + end if + call mark_file_end(unit_number) rewind(unit_number) - read(unit_number, '(I1)', iostat=iostatus, advance='no') returncode - if(iostatus /= 0) then - returncode = STATUS_BADRESPONSE - else - call read_mimetype(unit_number, return_type) - end if else diff --git a/render.f90 b/render.f90 index c8f1333..97458e0 100644 --- a/render.f90 +++ b/render.f90 @@ -45,6 +45,7 @@ implicit none procedure::render_proportional procedure::type_supported procedure::status_ready + procedure::report_unsupported_protocol procedure(initialize), deferred::initialize procedure(prepare_for_layout), deferred::prepare_for_layout @@ -239,6 +240,16 @@ contains end subroutine status_ready + subroutine report_unsupported_protocol(self, url) + implicit none + + class(renderer)::self + character(*), intent(in)::url + + call self%draw_error("Only gemini:// URLs supported ("//url//")") + + end subroutine report_unsupported_protocol + function width_of_line(r, text, startpos, endpos, heading_level, list_item) implicit none -- cgit v1.2.3