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