aboutsummaryrefslogtreecommitdiff
path: root/src/3rd/flibs/src/cgi/cgi_protocol.f90
blob: 6ce9f0bbc5e18c82692d706129ee257e9574ce46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
! cgi_protocol.f90 --
!     Module for interfacing with a web server via the CGI protocol
!     (GET, POST, ...)
!
!     TODO:
!     - find out how to deal with text area data
!     - find out how to deal with uploaded files
!     - merge environment variables into the cgi_get routines
!     - implement the SCGI protocol
!     - implement delayed responses (also via customisable template)
!
module cgi_protocol
    implicit none

    integer, parameter :: DICT_KEY_LENGTH    = 80
    integer, parameter :: DICT_VALUE_LENGTH  = 200
    integer, parameter :: DICT_BUFFER_LENGTH = DICT_KEY_LENGTH + DICT_VALUE_LENGTH + 1

    integer, parameter :: output_no_header    = 0
    integer, parameter :: output_html         = 1
    integer, parameter :: output_text         = 2
    integer, parameter, private :: output_html_delayed = 3  ! Not implemented yet!
    integer, parameter, private :: output_text_delayed = 4

    type DICT_DATA
        character(len=DICT_VALUE_LENGTH) :: value
    end type DICT_DATA

    interface cgi_get
        module procedure cgi_get_string
        module procedure cgi_get_integer
        module procedure cgi_get_real
    end interface

    type(DICT_DATA), parameter :: dict_null = dict_data('')

    integer, private, save     :: method    = -1
    integer, private, save     :: luout_cgi = -1
    logical, private, save     :: header_written

!
! Body of source code for storing and retrieving the data
! (also contains the CONTAINS clause)
!
include 'dictionary.f90'

! cgi_begin --
!     Determine the type of interaction with the server and retrieve the data
!
! Arguments:
!     html           Whether the output will be HTML or plain text
!     dict           Dictionary holding the information from the server (output)
!     luout          LU-number for writing the file (output!)
!
! Note:
!     This routine determines the way the server passes on the data
!     by looking for clues in the environment variables QUERY_LENGTH
!     and QUERY_STRING
!
!
! TODO:
!     Support for two-pass run (if the computation takes a longer
!     than a few seconds)
!
subroutine cgi_begin( html, dict, luout )
    integer, intent(in)               :: html
    type(DICT_STRUCT), pointer        :: dict
    integer, intent(out)              :: luout

    integer                           :: length
    integer                           :: status
    logical                           :: opend
    character(len=DICT_BUFFER_LENGTH) :: string
    character(len=1)                  :: ch

    !
    ! Clean up, if necessary
    !
    if ( associated(dict) ) then
        call dict_destroy( dict )
    endif

    header_written = .false.

    !
    ! Determine which input method
    !
    call get_environment_variable( "QUERY_STRING", length=length, status=status )
    if ( status == 0 ) then
        call cgi_get_method( dict, length )
        method = 1
    else
        call get_environment_variable( "CONTENT_LENGTH", value=string, status=status )
        if ( status == 0 ) then
            read( string, * ) length
            call cgi_post_method( dict, length )
            method = 1
        else
            read( *, '(A)', advance = 'no' ) ch
            if ( ch == '%' ) then
                !
                ! TODO: better method for determining length
                call cgi_dustmote_method( dict )
                method = 2
            elseif ( index( '1234567890', ch ) > 0 ) then
   !            call cgi_simple_cgi( dict )
                method = -1
            else
                method = -1
            endif
        endif
    endif

    !
    ! If we did not get the correct information, just blow the
    ! whole thing off
    !
    if ( method == -1 ) then
        call cgi_error( "CGI protocol not recognised or not implemented" )
    endif

    !
    ! What LU-number for the output
    ! method 1: write directly to standard output (assumed to be at unit 6)
    ! method 2: write to a file first (cgiout)
    !
    if ( method == 1 ) then
        luout = 6
    endif
    if ( method == 2 ) then
        do luout = 10,99
            inquire( luout, opened = opend )
            if ( .not. opend ) then
                exit
            endif
        enddo
        open( luout, file = 'cgiout' )
    endif
    luout_cgi = luout

    !
    ! Write the header lines
    !
    select case ( html )
        case ( output_html, output_text )
            call cgi_header( html )
        case( output_html_delayed, output_text_delayed )
            ! TODO
        case( output_no_header )
            ! Writing the header is delayed, because the type is not known yet
        case default
            call cgi_error( "Programming error: wrong value for parameter 'html' in CGI_BEGIN" )
    end select

end subroutine cgi_begin

! cgi_header --
!     Write the CGI header information
!
! Arguments:
!     type           Type of header
!
subroutine cgi_header( type )
    integer, intent(in) :: type

    header_written = .true.

    select case ( type )
        case ( output_html, output_html_delayed )
            write( luout_cgi, '(a)' ) 'Content-Type: text/html;charset=iso8859-1'
            write( luout_cgi, '(a)' ) ''
        case( output_text, output_text_delayed )
            write( luout_cgi, '(a)' ) 'Content-Type: text/plain;charset=iso8859-1'
            write( luout_cgi, '(a)' ) ''
        case( output_no_header )
            call cgi_error( "Programming error: value 'output_no_header' not allowed in CGI_HEADER" )
        case default
            call cgi_error( "Programming error: wrong value for parameter 'type' in CGI_HEADER" )
    end select
end subroutine cgi_header

! cgi_get_method --
!     Get the information via the environment variable QUERY_STRING
!
! Arguments:
!     dict           Dictionary holding the information from the server (output)
!     length         Total length of the input
!
subroutine cgi_get_method( dict, length )
    type(DICT_STRUCT), pointer :: dict
    integer, intent(in)        :: length

    character(len=length)      :: buffer

    call get_environment_variable( "QUERY_STRING", value=buffer )
    call cgi_store_dict( dict, buffer )

end subroutine cgi_get_method

! cgi_post_method --
!     Get the information via standard input
!
! Arguments:
!     dict           Dictionary holding the information from the server (output)
!     length         Total length of the input
!
subroutine cgi_post_method( dict, length )
    type(DICT_STRUCT), pointer :: dict
    integer, intent(in)        :: length

    character(len=length)      :: buffer

    read( *, '(a)', advance='no' ) buffer
    call cgi_store_dict( dict, buffer )

end subroutine cgi_post_method

! cgi_dustmote_method --
!     Get the information line by line
!
! Arguments:
!     dict           Dictionary holding the information from the server (output)
!
!
subroutine cgi_dustmote_method( dict )
    type(DICT_STRUCT), pointer :: dict

    type(DICT_DATA)            :: data
    character(len=DICT_KEY_LENGTH)    :: key
    character(len=DICT_BUFFER_LENGTH) :: input
    integer                    :: k
    integer                    :: lu
    integer                    :: ierr
    logical                    :: opend

    read( *, '(a)' ) input ! Skip the remainder of the first line
    read( *, '(a)' ) input

    do while ( input /= '%END%' )
        call cgi_store_dict( dict, input )

        read( *, '(a)', iostat=ierr ) input
        if ( ierr /= 0 ) then
            exit
        endif
    enddo

end subroutine cgi_dustmote_method

! cgi_store_dict --
!     Store the information in the dictionary
!
! Arguments:
!     dict           Dictionary holding all information
!     string         Complete string received from CGI server
!
subroutine cgi_store_dict( dict, string )
    type(DICT_STRUCT), pointer      :: dict
    character(len=*), intent(in)    :: string

    character(len=DICT_KEY_LENGTH)  :: key
    character(len=len(string))      :: buffer
    type(DICT_DATA)                 :: data

    integer                         :: k
    integer                         :: keq

    buffer = string

    do
        k = index( buffer, '&' )
        if ( k .le. 0 ) then
            if ( buffer == ' ' ) then
                exit
            else
                k = len(buffer) + 1   ! Remaining piece
            endif
        endif

        call cgi_decode_string( buffer(1:k-1) )

        !
        ! Store the string
        !
        keq = index( buffer(1:k-1), '=' )
        if ( keq > 0 ) then
            key = buffer(1:keq-1)
            data%value = buffer(keq+1:k-1)

            if ( .not. associated( dict ) ) then
                call dict_create( dict, key, data )
            else
                call dict_add_key( dict, key, data )
            endif
        endif

        if ( k < len(buffer) ) then
            buffer = buffer(k+1:)
        else
            buffer = ' '
        endif

    enddo
end subroutine cgi_store_dict

! cgi_decode_string --
!     Decode the string (replace + and %xx)
!
! Arguments:
!     dict           Dictionary holding all information
!     string         Complete string received from CGI server
!
subroutine cgi_decode_string( string )
    character(len=*), intent(inout) :: string

    integer :: k
    integer :: ch

    !
    ! First the +'s
    !
    do
        k = index( string, '+' )
        if ( k .le. 0 ) exit

        string(k:k) = ' '
    enddo

    !
    ! Now %xx
    !
    do
        k = index( string, '%' )
        if ( k .le. 0 ) exit

        read( string(k+1:k+2), '(z2)' ) ch
        string(k:) = achar(ch) // string(k+3:)
    enddo
end subroutine cgi_decode_string

! cgi_end --
!     Indicate to the server that we are done
! Arguments:
!     None
! Note:
!     This is simply done by writing a file cgiready,
!     if method 2 is used. Stop in all cases
!
subroutine cgi_end

    integer :: lu
    logical :: opend

    if ( method == 2 ) then
        do lu = 10,99
            inquire( lu, opened=opend )
            if ( .not. opend ) then
                open( lu, file = "cgiready" )
                close( lu )
                exit
            endif
        enddo
    endif

    stop

end subroutine cgi_end

! cgi_error --
!     Report a fatal error
! Arguments:
!     msg               Message to be printed
!     template          Template file to be used (optional)
!
subroutine cgi_error( msg, template )
    character(len=*), intent(in)           :: msg
    character(len=*), intent(in), optional :: template

    character(len=200)                     :: text
    integer :: k
    integer :: ierr
    integer :: lu
    logical :: opend
    logical :: exists

    exists = .false.
    if ( present(template) ) then
        inquire( file = template, exist = exists )
    endif

    if ( .not. header_written ) then
        write( luout_cgi, '(a)' ) 'Content-Type: text/html;charset=iso8859-1'
        write( luout_cgi, '(a)' ) ''
    endif

    if ( exists ) then
        do lu = 10,99
            inquire( lu, opened = opend )
            if ( .not. opend ) then
                exit
            endif
        enddo
        open( lu, file = template )

        do
            read( lu, '(a)', iostat=ierr ) text
            if ( ierr /= 0 ) exit

            k = index( text, 'MSG' )
            if ( k > 0 ) then
                write( luout_cgi, '(3a)' ) text(1:k-1), trim(msg), text(k+3:)
            else
                write( luout_cgi, '(a)' ) text
            endif
        enddo
        close( lu )
    else
        write( luout_cgi, * ) '<html>'
        write( luout_cgi, * ) '<head><title>Severe error</title></head>'
        write( luout_cgi, * ) '<body>'
        write( luout_cgi, * ) '<b>', trim(msg), '</b>'
        write( luout_cgi, * ) '</body></html>'
    endif

    call cgi_end

end subroutine cgi_error

! cgi_get_session --
!     Get the value of the "sessionid" variable
! Arguments:
!     dict          Dictionary with values
!     value         Value of the session ID (character(len=20))
! Note:
!     The session ID can be used to uniquely identify the
!     connection with the user. But it should be passed into the
!     HTML output as a hidden variable (see the documentation
!     for more information)
!
subroutine cgi_get_session( dict, value )
    type(DICT_STRUCT), pointer :: dict
    character(len=*)           :: value

    character(len=20)          :: time_string
    type(DICT_DATA)            :: data

    if ( dict_has_key( dict, "sessionid" ) ) then
        data = dict_get_key( dict, "sessionid" )
        value = data%value
    else
        call date_and_time( time = time_string )
        value = time_string(5:6) // time_string(8:10)
        data%value = value
        call dict_add_key( dict, "sessionid", data )
    endif

end subroutine cgi_get_session

! cgi_get_* --
!     Get the value of variables
! Arguments:
!     dict          Dictionary with values
!     varname       Name of the variable to retrieve
!     value         Value of the variable
! Note:
!     If the variable does not exist, then the value
!     is not changed! (Use dict_has_key() to check the
!     existence)
!
subroutine cgi_get_string( dict, varname, value )
    type(DICT_STRUCT), pointer :: dict
    character(len=*)           :: varname
    character(len=*)           :: value

    type(DICT_DATA)            :: data

    if ( dict_has_key( dict, varname ) ) then
        data = dict_get_key( dict, varname )
        value = data%value
    endif

end subroutine cgi_get_string

subroutine cgi_get_integer( dict, varname, value )
    type(DICT_STRUCT), pointer :: dict
    character(len=*)           :: varname
    integer                    :: value

    type(DICT_DATA)            :: data
    integer                    :: ierr
    integer                    :: new_value

    if ( dict_has_key( dict, varname ) ) then
        data = dict_get_key( dict, varname )
        read( data%value, *, iostat=ierr ) new_value
        if ( ierr == 0 ) then
            value = new_value
        endif
    endif

end subroutine cgi_get_integer

subroutine cgi_get_real( dict, varname, value )
    type(DICT_STRUCT), pointer :: dict
    character(len=*)           :: varname
    real                       :: value

    type(DICT_DATA)            :: data
    integer                    :: ierr
    real                       :: new_value

    if ( dict_has_key( dict, varname ) ) then
        data = dict_get_key( dict, varname )
        read( data%value, *, iostat=ierr ) new_value
        if ( ierr == 0 ) then
            value = new_value
        endif
    endif

end subroutine cgi_get_real

subroutine cgi_get_logical( dict, varname, value )
    type(DICT_STRUCT), pointer :: dict
    character(len=*)           :: varname
    logical                    :: value

    type(DICT_DATA)            :: data
    integer                    :: ierr
    integer                    :: new_value

    if ( dict_has_key( dict, varname ) ) then
        data = dict_get_key( dict, varname )
        read( data%value, *, iostat=ierr ) new_value
        if ( ierr == 0 ) then
            value = (new_value == 1)
        endif
    endif

end subroutine cgi_get_logical

end module cgi_protocol