aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--binary.f9063
-rw-r--r--dumb_binary.f9092
-rw-r--r--gemini.prj6
-rw-r--r--main.F906
-rw-r--r--protocol.f90144
-rw-r--r--render.f9011
6 files changed, 289 insertions, 33 deletions
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 <jeff@rainbow-100.com>
+!
+! 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 <jeff@rainbow-100.com>
+!
+! 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