files
simple-httpd/httpd.asm
Mikhail Frolov eb72cf5cb9 fix bugs and update sys_func.inc code
fixed size sockaddr struct
update function CreateThread
clear code in main file
2025-05-23 08:12:42 +05:00

399 lines
11 KiB
NASM

;*****************************************************************************;
; Copyright (C) 2023-2024, Mikhail Frolov aka Doczom . All rights reserved. ;
; Distributed under terms of the 3-Clause BSD License. ;
; ;
; httpd - Simple http server for Kolibri OS. ;
; ;
; Version 0.2.5, 13 June 2024 ;
; ;
;*****************************************************************************;
HTTPD_FIRST_REQUEST_BUFFER = 0x8000
;include "macros.inc"
use32
org 0
db 'MENUET01'
dd 1, START, I_END, MEM, STACKTOP
M01header.params:
dd PATH, 0
include "macros.inc"
include 'module_api.inc'
include "proc32.inc"
include "dll.inc"
include "KOSfuncs.inc"
;include 'D:\kos\programs\network.inc'
;KOS_APP_START
include 'sys_func.inc'
include 'settings.inc'
;CODE
START:
call InitHeap
mcall SF_SET_EVENTS_MASK, EVM_STACK ;set event bitmap
; init library
stdcall dll.Load, @IMPORT
test eax, eax
jnz .err_settings
mov ecx, default_ini_path
mov eax, [M01header.params]
cmp byte[eax],0
jz .load_settings
mov edx, ' '
mov ecx, eax
cmp byte[eax], '"'
jne @f
mov dl, '"'
inc ecx
@@:
mov esi, ecx
@@:
lodsb
test al, al
jz @f
cmp al, dl
jne @b
@@:
mov byte[esi - 1], 0
.load_settings:
; get settings
call load_settings ; ecx -> string to config file
test eax, eax
jnz .err_settings
;init server socket
push dword SO_NONBLOCK ;IPPROTO_TCP
push dword SOCK_STREAM
push dword AF_INET4
call netfunc_socket; AF_INET4, SOCK_STREAM, SO_NONBLOCK ; we dont want to block on accept
cmp eax, -1
je .sock_err
mov [srv_socket], eax
stdcall netfunc_bind, [srv_socket], srv_sockaddr, srv_sockaddr.length
cmp eax, -1
je .bind_err
; listen()
stdcall netfunc_listen, [srv_socket], [srv_backlog]
cmp eax, -1
jz .listen_err
.mainloop:
cmp dword[srv_shutdown], 1
jz .shutdown
mcall 23, 100 ; get event to network stack
test eax, eax
jz .mainloop
cmp dword[srv_stop], 1
jz .mainloop
push dword thread_connect
call CreateThread ; not save PID
jmp .mainloop
.shutdown:
.listen_err:
.bind_err:
stdcall netfunc_close, [srv_socket]
.err_settings:
.sock_err:
ThreadExit
;-----------------------------------------------------------------------------
thread_connect:
sub esp, sizeof.CONNECT_DATA
mcall SF_SET_EVENTS_MASK, EVM_STACK ; set event - network event
; Accept - wait connection, get socket and sockaddr
lea edx, [esp + CONNECT_DATA.sockaddr] ; new sockaddr
push sizeof.sockaddr_in
push edx
push dword[srv_socket]
call netfunc_accept
cmp eax, -1
jz .err_accept
; connection is enable
mov [esp + CONNECT_DATA.socket], eax
; теперь нужно выделить буфер(тоже на 16 кб наверное), и
; прочитать в этот буфер из сокета, когда прочтём ноль(или больше 4 кб), тогда
; выходим из цикла и анализируем заголовки и стартовую стоку.
stdcall Alloc, HTTPD_FIRST_REQUEST_BUFFER ;alloc memory 32 kib
test eax, eax
jz .err_alloc
mov [esp + CONNECT_DATA.buffer_request], eax
mov edx, eax
mov esi, esp ; SAVE CONNECT_DATA
mov dword[esi + CONNECT_DATA.request_size], 0
@@:
;read data from socket
push dword 0 ;flags
mov eax, [esi + CONNECT_DATA.request_size]
push dword HTTPD_FIRST_REQUEST_BUFFER
sub [esp], eax
push dword[esi + CONNECT_DATA.buffer_request]
add [esp], eax
push dword[esi + CONNECT_DATA.socket]
call netfunc_recv
cmp eax, -1
jz .err_recv_sock
test eax, eax
jz @f
add [esi + CONNECT_DATA.request_size], eax
; cmp [esi + CONNECT_DATA.request_size], HTTPD_FIRST_REQUEST_BUFFER ; check end buffer
; jb @b
@@:
; после получения всего запроса(более или менее всего) выделяем озу для
; ассоциативного массива заголовков и аргументов запроса
; 8*50 + 8*100
; esp .. esp + 1024 -> for http headers
; esp + 1024 .. esp + 2048 -> for URI args
sub esp, 2048
; parse http message
mov ecx, [esi + CONNECT_DATA.buffer_request]
call parse_http_query ; ecx - buffer
test eax, eax
jz .err_parse
; find unit for uri path
cmp dword[GLOBAL_DATA.modules], 0
jz .no_modules
mov eax, [GLOBAL_DATA.modules]
.next_unit:
push esi edi
mov esi, [esi + CONNECT_DATA.uri_path]
lea edi, [eax + HTTPD_MODULE.uri_path]
; check module for all uri path
@@:
cmpsb
jne @f
cmp byte[edi - 1], 0
jne @b
.found_module:
; found module
pop edi esi
push dword[eax + HTTPD_MODULE.pdata] ; context of module
push esi ; coutext of request
call dword[eax + HTTPD_MODULE.httpd_serv] ; call unit function
jmp .end_work
@@:
.found_special_char:
cmp byte[edi - 1], '*'
jne .no_special_char
;je .found_module
; check next char in uri of modules
cmp byte[edi], 0
je .found_module
; uri path "*name" or "*name*"
dec esi
.special_char_loop:
cmp byte[esi], 0
je .no_special_char
;inc esi
xor ecx, ecx
@@:
inc ecx
mov dl, byte[edi + ecx - 1]
cmp dl, '*'
je @f
cmp byte[esi + ecx - 1], dl
lea esi, [esi + 1]
jne .special_char_loop
dec esi
test dl, dl
jnz @b
jmp .found_module
@@:
; found substr
add esi, ecx
add edi, ecx
jmp .found_special_char
.no_special_char:
pop edi esi
mov eax, [eax] ; HTTPD_MODULE.next
test eax, eax ; terminate list
jne .next_unit
.no_modules:
; if not found modules, call file_server
call file_server ; esi - struct
; end work thread
jmp .end_work
.err_parse:
call file_server.err_http_501
.end_work:
add esp, 2048
.err_recv_sock:
; free IN buffer
cmp dword[esp + CONNECT_DATA.buffer_request], 0
jz .err_alloc
stdcall Free, [esp + CONNECT_DATA.buffer_request]
.err_alloc:
stdcall netfunc_close, [esp + CONNECT_DATA.socket]
.err_accept:
add esp, sizeof.CONNECT_DATA
; close this thread
ret
include 'parser.inc'
include 'file_server.inc'
; DATA AND FUNCTION
include 'httpd_lib.inc'
I_END:
;DATA
@IMPORT:
library libini, 'libini.obj'
import libini,\
ini.get_str, 'ini_get_str',\
ini.get_int, 'ini_get_int',\
ini.enum_keys, 'ini_enum_keys'
default_ini_path: db 'httpd.ini',0
ini_section_units: db 'MODULES',0
ini_section_main: db 'MAIN', 0
ini_section_tls db 'TLS',0
ini_key_ip db 'ip',0
ini_key_port db 'port',0
ini_key_conn db 'conn',0
ini_key_flags db 'flags',0
ini_key_work_dir db 'work_dir',0
ini_key_modules_dir db 'modules_dir',0
ini_key_mime_file db 'mime_file',0
ini_key_mbedtls_obj db 'mbedtls',0
ini_key_ca_cer db 'ca_cer',0
ini_key_srv_crt db 'srv_crt',0
ini_key_srv_key db 'srv_key',0
httpd_module_init db 'httpd_init',0
httpd_module_serv db 'httpd_serv',0
httpd_module_close db 'httpd_close',0
IMPORT_MODULE:
dd httpd_import, GLOBAL_DATA.modules_dir, 0
httpd_import:
.init dd httpd_module_init
.serv dd httpd_module_serv
.close dd httpd_module_close
dd 0
EXPORT_DATA: ; in modules for this table using struct IMPORT_DATA
dd API_VERSION
dd .size
dd netfunc_socket
dd netfunc_close
dd netfunc_bind
dd netfunc_accept
dd netfunc_listen
dd netfunc_recv
dd netfunc_send
dd Alloc
dd Free
dd parse_http_query ; no stdcall
dd FileInitFILED
dd FileInfo
dd FileRead
dd FileSetOffset
dd FileReadOfName
dd send_resp
dd create_resp
dd destruct_resp
dd set_http_status
dd add_http_header
dd del_http_header
dd set_http_ver
dd begin_send_resp
dd finish_send_resp
dd find_uri_arg
dd find_header
dd read_http_body
dd Get_MIME_Type
dd close_server
dd GLOBAL_DATA
.size = $ - EXPORT_DATA ; (count func)*4 + size(api ver) + 4
dd 0
; DATA
;UDATA
srv_stop: rd 1 ; set 1 for skip new connections
srv_shutdown: rd 1 ; set 1 for ending working server
srv_tls_enable rd 1 ; set 1 for enable TLS mode working
srv_backlog: rd 1 ; maximum number of simultaneous open connections
srv_socket: rd 1
srv_sockaddr:
rw 1
.port rw 1
.ip rd 1
rb 8
.length = $ - srv_sockaddr
GLOBAL_DATA:
.modules rd 1 ; pointer to a doubly connected non-cyclic list (null terminator)
; next, prev, ptr of httpd_serv(), uri path
.work_dir rb 1024 ; max size path to work directory
.work_dir.size rd 1 ; length string
.modules_dir rb 1024
.modules_dir.end rd 1
.MIME_types_arr rd 1
.flags rd 1
._module_cmd rd 1
PATH:
rb 256
; stack memory
rb 4096
STACKTOP:
MEM:
;KOS_APP_END