diff --git a/httpd.asm b/httpd.asm
new file mode 100644
index 0000000..82f4686
--- /dev/null
+++ b/httpd.asm
@@ -0,0 +1,192 @@
+;*****************************************************************************;
+; Copyright (C) 2023, 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.0.1, 12 November 2023 ;
+; ;
+;*****************************************************************************;
+use32
+org 0
+
+
+include 'sys_func.inc'
+include 'settings.inc'
+
+
+start:
+ mcall 68, 11 ; init heap
+ mcall 40, EVM_STACK ;set event bitmap
+
+ mov ecx, PATH
+ cmp byte[ecx],0
+ jnz @f
+ mov ecx, default_ini_path
+@@:
+ ; get settings
+ call load_settings ; ecx -> string to config file
+ test eax, eax
+ jnz .err_settings
+
+ ;init server socket
+ push dword SO_NONBLOCK
+ 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
+
+ push srv_sockaddr.length
+ push dword srv_sockaddr
+ push dword[srv_socket]
+ call netfunc_bind; [srv_socket], srv_sockaddr, srv_sockaddr.length
+ cmp eax, -1
+ je .bind_err
+
+ ; listen()
+ push dword[srv_backlog]
+ push dword[srv_socket]
+ call netfunc_listen; [srv_socket], [srv_backlog]
+ cmp eax, -1
+ jz .listen_err
+
+.mainloop:
+ mcall 23, 100 ; get event to network stack
+ test eax, eax
+ jz .mainloop
+
+ push dword thread_connect
+ call CreateThread ; not save PID
+ jmp .mainloop
+
+.listen_err:
+.bind_err:
+ push dword[srv_socket]
+ call close; [srv_socket]
+
+..err_settings:
+.sock_err:
+ mcall -1
+
+
+
+thread_connect:
+ sub esp, sizeof.CONNECT_DATA
+ mcall 40, EVM_STACK ; set event bitmap - network event
+
+ ; ожидание подключения Accept, sockaddr находится на вершине стека нового потока
+ lea edx, [esp + CONNECT_DATA.sockaddr] ; new sockaddr
+ push dword 16 ; 16 byte - sockaddr length
+ push edx
+ push dword[srv_socket]
+ call netfunc_accept
+
+ cmp eax, -1
+ jz .err_accept
+
+ mov [esp + CONNECT_DATA.socket], eax
+ mov ebp, esp
+
+ ; соединение установленно, теперь нужно выделить буфер(тоже на 16 кб наверное), и
+ ; прочитать в этот буфер из сокета, когда прочтём ноль(или больше 4 кб), тогда
+ ; выходим из цикла и анализируем заголовки и стартовую стоку.
+ mov esi, 0x8000
+ push esi
+ call Alloc ;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 0x8000
+ 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], 0x8000 ; 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
+ call parse_http_query ; ecx - buffer edx - length data in buffer
+ test eax, eax
+ jz .err_parse
+ ; вызов нужной функции из списка моделей
+
+ ; TODO
+
+ ; end work thread
+ jmp .end_work
+
+
+.err_parse:
+ ; send error 501
+
+.end_work:
+ add esp, 2048
+ ; free OUT buffer
+ cmp dword[esp + CONNECT_DATA.buffer_response], 0
+ jz .err_recv_sock
+ push dword[esp + CONNECT_DATA.buffer_response]
+ call Free
+.err_recv_sock:
+ ; free IN buffer
+ cmp dword[esp + CONNECT_DATA.buffer_request], 0
+ jz .err_alloc
+ push dword[esp + CONNECT_DATA.buffer_request]
+ call Free
+.err_alloc:
+ push dword[esp + CONNECT_DATA.socket]
+ call netfunc_close
+.err_accept:
+ lea ecx,[esp + sizeof.CONNECT_DATA - 0x4000] ; get pointer to alloc memory
+ mcall 68, 13 ; free
+ mcall -1 ; close thread
+
+include 'parser.inc'
+
+; DATA AND FUNCTION
+include 'httpd_lib.inc'
+
+; DATA
+
+srv_backlog: dd 0 ; максимум одновременных подключений подключений
+
+srv_socket: dd 0
+
+srv_sockaddr:
+ dw AF_INET4
+ .port dw 0
+ .ip dd 0
+ rb 8
+ .length = $ - srv_sockaddr
+
+GLOBAL_DATA:
+ .units dd 0 ; указатель на ассоциативный массив пути и указателя на функцию либы(см ниж)
+ .unit_count dd 0 ; количество записей в массиве
+ .libs dd 0 ; указатель на массив указателей на ассоциативные массивы библиотек
+;; .flags dd 0 ; 1 - all hosts(элемент hosts не указатель на массив, а на функцию)
+
+
diff --git a/httpd.ini b/httpd.ini
new file mode 100644
index 0000000..cf72d17
--- /dev/null
+++ b/httpd.ini
@@ -0,0 +1,25 @@
+[MAIN]
+# server IPv4 address. Non IPv6 address
+ip=127.0.0.1
+# server port number
+port=80
+# count open connection
+conn=100
+
+# 1000 - no parse http headers, raw data in CONNECT_DATA.message_body
+# This flags using for http/2.0 and other protocol execution.
+flags=0000 # parsing http headers
+
+# directory for find files
+work_dir=/sys/http_server/data
+# directory for find lib
+units_dir=/sys/http_units
+
+[UNITS]
+# list units
+# path = path to lib in units_dir
+database/sqlite3=sqlite3_serv.obj
+database/cvs=cvs_table_server.obj
+
+# server called function httpd_unit_func(CONNECT_DATA* struct_server );
+# for init unit, server called function httpd_unit_init(void* global_data);
\ No newline at end of file
diff --git a/httpd_lib.inc b/httpd_lib.inc
new file mode 100644
index 0000000..212e2b4
--- /dev/null
+++ b/httpd_lib.inc
@@ -0,0 +1,109 @@
+
+align 4
+day:
+ dd 'Mon,'
+ dd 'Tue,'
+ dd 'Wed,'
+ dd 'Thu,'
+ dd 'Fri,'
+ dd 'Sat,'
+ dd 'Sun,'
+.count = ($ - day) / 4
+
+align 4
+months:
+ dd 'Jan '
+ dd 'Feb '
+ dd 'Mar '
+ dd 'Apr '
+ dd 'May '
+ dd 'Jun '
+ dd 'Jul '
+ dd 'Aug '
+ dd 'Sep '
+ dd 'Oct '
+ dd 'Nov '
+ dd 'Dec '
+.count = ($ - months) / 4 ; count item in this array
+
+; HTTP-date = rfc1123-date | rfc850-date | asctime-date
+; rfc1123-date = wkday "," SP date1 SP time SP "GMT"
+; rfc850-date = weekday "," SP date2 SP time SP "GMT"
+; asctime-date = wkday SP date3 SP time SP 4DIGIT
+; date1 = 2DIGIT SP month SP 4DIGIT
+; ; day month year (e.g., 02 Jun 1982)
+; date2 = 2DIGIT "-" month "-" 2DIGIT
+; ; day-month-year (e.g., 02-Jun-82)
+; date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
+; ; month day (e.g., Jun 2)
+; time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+; ; 00:00:00 - 23:59:59
+; wkday = "Mon" | "Tue" | "Wed"
+; | "Thu" | "Fri" | "Sat" | "Sun"
+; weekday = "Monday" | "Tuesday" | "Wednesday"
+; | "Thursday" | "Friday" | "Saturday" | "Sunday"
+; month = "Jan" | "Feb" | "Mar" | "Apr"
+; | "May" | "Jun" | "Jul" | "Aug"
+; | "Sep" | "Oct" | "Nov" | "Dec"
+
+
+
+
+serv_header:
+ .Accept_Ranges db 'Accept-Ranges: bytes',13,10
+ .connection db 'Connection: close',0
+
+http_method:
+ .get: db 'GET '
+ .head: db 'HEAD'
+ .post: db 'POST'
+ .put: db 'PUT '
+ .patch db 'PATCH'
+
+;error_404:
+; db ''
+; db '
Error 404'
+; db ''
+; db 'Error 404
'
+; db 'The server could not find the requested page.
'
+; db ''
+; db '',0
+
+http_err_response:
+ db 'HTTP/1.1 '
+.code = $ - http_err_response
+ db '000 ',13, 10
+ db 'Server: simple-httpd/0.0.1', 13, 10
+ db 'Date: '
+.date = $ - http_err_response
+ db 'Sun, 30 Oct 2022 09:29:13 GMT',13, 10
+ db 'Content-length: 0', 13, 10
+ db 'Content-type: text/plain', 13, 10;
+ db 'Connection: close', 13, 10
+ db 13, 10
+.size = $ - http_err_response
+
+
+base_response:
+label response at 0
+ db 'HTTP/1.0 '
+.code: db '000 ',13, 10
+ db 'Server: httpd(kolibri os)/0.0.1', 13, 10
+ db 'Cache-Control: no-cache', 13, 10
+ db 'Content-Encoding: '
+.content_encod: db 'identity', 13, 10
+ db 'Date: '
+.date: db 'Sun, 30 Oct 2022 09:29:13 GMT',13, 10
+ db 'Content-length: '
+.content_len: db ' ', 13, 10
+ db 'Content-type: '
+.content_type: db ' ', 13, 10;
+ ;'text/html; charset=utf-8'
+.end_headers: ;нужно, когда базового заголовка не хватает
+.connection db 'Connection: close', 13, 10
+ db 13, 10
+.body: ; с этого оффсета уже писать данные
+
+; min HTTP request size
+; "GET / HTTP/1.1" - 18 byte
+min_http_size = 18
diff --git a/parser.inc b/parser.inc
new file mode 100644
index 0000000..38a6b61
--- /dev/null
+++ b/parser.inc
@@ -0,0 +1,254 @@
+
+BASE_ARRAY_ARGS equ (esi - 1024)
+BASE_ARRAY_HEADERS equ (esi - 2048)
+
+; IN:
+; esi - struct
+; ecx = ptr to str URI
+; OUT:
+; ecx - new base for reading data ('HTTP/1.1 ...')
+; eax -
+; NOTE: this function don`t check buffer size
+parse_url:
+; URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+;
+; hier-part = "//" authority path-abempty
+; / path-absolute
+; / path-rootless
+; / path-empty
+;
+; foo://example.com:8042/over/there?name=ferret#nose
+; \_/ \______________/\_________/ \_________/ \__/
+; | | | | |
+; scheme authority path query fragment
+; | _____________________|__
+; / \ / \
+; urn:example:animal:ferret:nose
+
+ cmp byte[ecx], '/' ; check abs-path
+ je .get_path
+
+ ;get scheme
+ mov [esi + CONNECT_DATA.uri_scheme], ecx
+@@:
+ inc ecx
+ cmp byte[ecx - 1], ':'
+ jne @b
+ mov byte[ecx - 1], 0
+
+ cmp word[ecx], '//'
+ jne .get_path
+
+ add ecx, 2
+ mov [esi + CONNECT_DATA.uri_authority], ecx
+ ;get authority
+@@:
+ inc ecx
+ ;cmp byte[ecx - 1], ' ' ;check end, не нужно, так как в http всегда / абс путь
+
+ cmp byte[ecx - 1], '/'
+ jne @b
+ dec ecx
+.get_path:
+ ;path-absolute
+ mov [esi + CONNECT_DATA.uri_path], ecx
+@@:
+ inc ecx
+ cmp byte[ecx], '?'
+ je .get_query
+
+ cmp byte[ecx], '#'
+ je get_fragment
+
+ cmp byte[ecx], ' ' ; check end path
+ jne @b
+ mov byte[ecx], 0
+ inc ecx
+ jmp .exit
+
+.get_query:
+ mov byte[ecx], 0
+ inc ecx
+ lea eax, [BASE_ARRAY_ARGS]
+ mov dword[esi + CONNECT_DATA.uri_arg], eax
+ xor edx, edx ; counter items
+ ; add new item
+.get_query_new_arg:
+ inc edx
+ mov dword[BASE_ARRAY_ARGS + (edx-1)*8], ecx
+ mov [esi + CONNECT_DATA.num_uri_args], edx
+ dec ecx
+@@:
+ inc ecx
+ cmp byte[ecx], '='
+ je .get_args
+
+ cmp byte[ecx], '#' ; ЭТО БРЕД ПОЛНЫЙ, НО ВДРУГ
+ je .get_fragment
+
+ cmp byte[ecx], ' ' ; http://cjkhr.bvgbfdkvdf.dmejfgehf/?1pr1
+ jne @b
+.exit_2:
+ mov byte[ecx], 0
+ inc ecx
+ jmp .exit
+
+.get_args:
+ mov byte[ecx], 0
+ inc ecx
+ mov dword[BASE_ARRAY_ARGS + (edx-1)*8 + 4], ecx
+ dec ecx
+@@:
+ inc ecx
+ cmp byte[ecx], '#'
+ je .get_fragment
+
+ cmp byte[ecx], ' '
+ je .exit_2
+
+ cmp byte[ecx] '&'
+ jne @b
+
+ jmp .get_query_new_arg
+
+.get_fragment:
+ cmp byte[ecx], '#'
+ jne .exit
+
+ mov byte[ecx], 0
+ inc ecx
+ mov [esi + CONNECT_DATA.uri_fragment], ecx
+@@:
+ inc ecx
+ cmp byte[ecx - 1], ' '
+ jne @b
+ mov [ecx - 1], 0
+ .exit:
+ ret
+
+
+; IN:
+; esi - struct
+; ecx - ptr to begin headers block
+; edx - free mem ptr
+; OUT:
+; ecx - new base for reading body message HTTP query
+; eax -
+; NOTE: this function don`t check buffer size
+parse_headers:
+ ; init array
+ mov [esi + CONNECT_DATA.num_headers], 0
+ mov [esi + CONNECT_DATA.http_headers], BASE_ARRAY_HEADERS
+ xor edx, edx
+ .new_str:
+ cmp word[ecx], 0x0A0D ; \n
+ jnz .find_header
+ ; end find heeaders
+ add ecx, 2 ; ecx = base for body message
+ ret
+
+.find_header:
+ ; add new item in array headers
+ inc edx
+ mov dword[esi + CONNECT_DATA.num_headers], edx
+ ; save pointer to name header
+ mov dword[BASE_ARRAY_HEADERS + (edx-1)*8], ecx
+ dec ecx
+ @@:
+ inc ecx
+
+ cmp byte[ecx], ':'
+ jnz @b
+
+ mov byte[ecx], 0 ; \0
+ inc ecx
+ ; save pointer to value
+ mov dword[BASE_ARRAY_HEADERS + (edx-1)*8 + 4], ecx
+@@:
+ inc ecx
+ cmp byte[ecx - 1], 0x0A0D
+ jnz @b
+ mov byte[ecx - 1], 0
+ inc ecx ; set offset on new string
+ jmp .new_str
+
+; IN:
+; ecx - raw data query
+; esi - ptr to CONNECT_DATA
+; OUT: eax = 0 error
+; eax = prt to struct CONNECT_DATA
+parse_http_query:
+;method scheme://host:port/abs_path HTTP/1.1 0x0d 0x0a
+;header_1:value 0x0d 0x0a
+; ...
+;header_N:value 0x0d 0x0a
+;0xd 0xa
+; message data
+
+ mov eax, [esi + CONNECT_DATA.request_size]
+ mov [esi + CONNECT_DATA.tmp_req_size], eax
+
+ ; check size
+ cmp dword[esi + CONNECT_DATA.request_size], min_http_size
+ jb .error_exit
+
+ ; get http METHOD this message
+ mov [esi + CONNECT_DATA.http_method], ecx
+@@:
+ inc ecx
+ cmp byte[ecx - 1], ' ' ; find end method
+ jnz @b
+ mov byte[ecx - 1], 0
+
+ ; check size
+ mov edx, ecx
+ sub edx, [esi + CONNECT_DATA.buffer_request]
+ sub edx, 2 ; / 0x20
+ cmp dword[esi + CONNECT_DATA.request_size], edx
+ jle .error_exit
+
+ ; ecx <- uri string
+ ; парсинг uri строки в заголовке запроса(получение схемы, пути аргументов, фрагмента и тд)
+ call parse_url
+
+ ; check size
+ mov edx, ecx
+ sub edx, [esi + CONNECT_DATA.buffer_request]
+ sub edx, 7 ; H/0.0 0x0d0 x0a
+ cmp dword[esi + CONNECT_DATA.request_size], edx
+ jle .error_exit
+
+ ; get http version(HTTP/1.1)
+ mov [esi + CONNECT_DATA.http_verion], ecx
+@@:
+ inc ecx
+ cmp word[ecx - 1], 0x0A0D
+ jnz @b
+ mov word[ecx - 1], 0
+ inc ecx ; <- start first header
+
+ ; check size
+ mov edx, ecx
+ sub edx, [esi + CONNECT_DATA.buffer_request]
+ sub edx, 2 ; 0x0d 0x0a
+ cmp dword[esi + CONNECT_DATA.request_size], edx
+ jle .error_exit
+
+ ; получение заголовков запроса (ключ + значение-строка)
+ call parse_headers
+
+
+ ; check size
+ mov edx, ecx
+ sub edx, [esi + CONNECT_DATA.buffer_request]
+ cmp dword[esi + CONNECT_DATA.request_size], edx
+ jl .error_exit
+
+ mov [esi + CONNECT_DATA.message_body], ecx
+ ; докачивается всё остальное
+ mov eax, esi
+ ret
+
+.error_exit:
+ xor eax, eax
+ ret
\ No newline at end of file
diff --git a/settings.inc b/settings.inc
new file mode 100644
index 0000000..4b36290
--- /dev/null
+++ b/settings.inc
@@ -0,0 +1,51 @@
+
+
+struct CONNECT_DATA ; 16*4 = 64 bytes
+ socket dd 0 ; номер сокета подключения
+ sockaddr dd 16/4 ; socaddr connection
+ buffer_request dd 0 ; pointer to buffer for geting message socket
+ request_size dd 0 ; size geted data from client
+ tmp_req_size dd 0 ; для парсера
+ buffer_response dd 0 ; pointer to buffwr for resp message
+ http_method dd 0 ; указатель на строку
+ http_verion dd 0 ; указатель на строку
+ num_headers dd 0 ; number items in REQUEST_DATA
+ http_headers dd 0 ; указатель на массив REQUEST_DATA
+ uri_scheme dd 0 ; указатель на схему
+ uri_authority dd 0 ; pointer to struct ?
+ uri_path dd 0 ; указатель на декодированный путь к ресурсу(без параметров)
+ num_uri_args dd 0 ;
+ uri_arg dd 0 ; pointer to array REQUEST_DATA аргументов uri строк
+ uri_fragment dd 0 ; указатель на строку
+ message_body dd 0 ; указатель на тело http запроса
+ends
+
+struct REQUEST_DATA
+ ptr_name dd 0 ;
+ ptr_data dd 0 ;
+ends
+
+; Load server config
+; ecx - path to file
+; OUT: eax - 0 or err_code
+load_settings:
+ ; check file path
+ sub esp, 40 ; size file info struct
+ push esp
+ push ecx
+ call FileInfo
+ lea esp, [esp + 40]
+ test eax, eax
+ jnz .err
+
+ ret
+.err:
+ ret
+
+; Config format:
+; Standart INI file:
+; - ";" or "#" comments
+; - [name] name of group
+; - arg=val params in group
+
+
diff --git a/sys_func.inc b/sys_func.inc
new file mode 100644
index 0000000..7b04e19
--- /dev/null
+++ b/sys_func.inc
@@ -0,0 +1,90 @@
+; ABSTRACT SYSTEM FUNCTIONS
+
+; stdcall socket(uint32_t domain, type, proto)
+netfunc_socket:
+ push ebx esi
+ mcall 75, 0, [esp + 2*4 + 4], [esp + 2*4 + 8], [esp + 2*4 + 12]
+ ;mov [fs:0], ebx ;errno
+ pop esi ebx
+ ret 12
+
+;stdcall close(uint32_t sock_number);
+netfunc_close:
+ push ebx
+ call 75, 1, [esp + 4 + 4]
+ pop ebx
+ ret 4
+
+;stdcall bind(uint32_t sock_num, sockaddr* _sockaddr_struct, uint32_t sockaddr_size)
+netfunc_bind:
+ push esi ebx
+ mcall 75, 2, [esp + 2*4 + 4], [esp + 2*4 + 8], [esp + 2*4 + 12]
+ pop ebx esi
+ ret 12
+
+;stdcall listen(uint32_t socket, uint32_t backlog)
+netfunc_listen:
+ push ebx
+ mcall 75, 3, [esp + 4 + 4], [esp + 4 + 8]
+ pop ebx
+ ret 8
+;stdcall accept(uint32_t socket, sockaddr* new_sockaddr_struct, uint32_t sockaddr_size)
+netfunc_accept:
+ push esi ebx
+ mcall 75, 5, [esp + 2*4 + 4], [esp + 2*4 + 8], [esp + 2*4 + 12]
+ pop ebx esi
+ ret 12
+;stdcall send(uint32_t socket, void* buff, uint32_t len_buff, uint32_t flags)
+netfunc_send:
+ push esi edi ebx
+ mcall 75, 6, [esp + 3*4 + 4], [esp + 3*4 + 8],\
+ [esp + 3*4 + 12], [esp + 3*4 + 16]
+ pop ebx edi esi
+ ret 16
+;stdcall recv(uint32_t socket, void* buff, uint32_t len_buff, uint32_t flags)
+netfunc_recv:
+ push esi edi ebx
+ mcall 75, 7, [esp + 3*4 + 4], [esp + 3*4 + 8],\
+ [esp + 3*4 + 12], [esp + 3*4 + 16]
+ pop ebx edi esi
+ ret 16
+
+; stdcall CreatThread(void* entry_thread);
+CreateThread:
+ push ebx edi
+ mcall 68, 12, 0x4000 ;alloc memory 16 kib for stack
+ test eax, eax
+ jz .err
+
+ mov ecx, 0x4000/4
+ mov edi, eax
+ ;start thread for new connection
+ mov edx, eax
+ xor eax, eax
+ rep stosd
+ add edx, 0x4000
+ mcall 51, 1, [esp + 2*4 + 4] ;<- thread entry
+.err:
+ pop ebx edi
+ ret 4
+; stdcall Alloc(uint32_t size)
+Alloc:
+ push ebx
+ mcall 68, 12, [esp + 4 + 4]
+ pop ebx
+ ret 4
+; stdcall Free(void* ptr)
+Free:
+ push ebx
+ mcall 68, 13, [esp + 4 + 4]
+ pop ebx
+ ret 4
+
+;NTSTATUS stdcall FileInfo(const char* path, void* buff)
+FileInfo:
+
+ ret 8
+;NTSATTUS stdcall FileRead(const char* path, void* buff, uint32_t size)
+FileRead:
+
+ ret 12
\ No newline at end of file