0,0 → 1,787 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; HTTP library for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
; references: |
; "HTTP made really easy", http://www.jmarshall.com/easy/http/ |
; "Hypertext Transfer Protocol -- HTTP/1.1", http://tools.ietf.org/html/rfc2616 |
|
|
URLMAXLEN = 65535 |
BUFFERSIZE = 4096 |
|
__DEBUG__ = 1 |
__DEBUG_LEVEL__ = 1 |
|
|
format MS COFF |
|
public @EXPORT as 'EXPORTS' |
|
include '../../../struct.inc' |
include '../../../proc32.inc' |
include '../../../macros.inc' |
purge section,mov,add,sub |
include '../../../debug-fdo.inc' |
|
include '../../../network.inc' |
include 'http.inc' |
|
virtual at 0 |
http_msg http_msg |
end virtual |
|
macro copy_till_zero { |
@@: |
lodsb |
test al, al |
jz @f |
stosb |
jmp @r |
@@: |
} |
|
section '.flat' code readable align 16 |
|
;;===========================================================================;; |
lib_init: ;//////////////////////////////////////////////////////////////////;; |
;;---------------------------------------------------------------------------;; |
;? Library entry point (called after library load) ;; |
;;---------------------------------------------------------------------------;; |
;> eax = pointer to memory allocation routine ;; |
;> ebx = pointer to memory freeing routine ;; |
;> ecx = pointer to memory reallocation routine ;; |
;> edx = pointer to library loading routine ;; |
;;---------------------------------------------------------------------------;; |
;< eax = 1 (fail) / 0 (ok) (library initialization result) ;; |
;;===========================================================================;; |
mov [mem.alloc], eax |
mov [mem.free], ebx |
mov [mem.realloc], ecx |
mov [dll.load], edx |
|
invoke dll.load, @IMPORT |
or eax, eax |
jz .ok |
|
; load proxy settings |
invoke ini.get_str, inifile, sec_proxy, key_proxy, proxyAddr, 256, proxyAddr |
invoke ini.get_int, inifile, sec_proxy, key_proxyport, 80 |
mov [proxyPort], eax |
invoke ini.get_str, inifile, sec_proxy, key_user, proxyUser, 256, proxyUser |
invoke ini.get_str, inifile, sec_proxy, key_password, proxyPassword, 256, proxyPassword |
|
xor eax, eax |
inc eax |
ret |
|
.ok: |
xor eax, eax |
ret |
|
|
|
|
|
;;================================================================================================;; |
proc HTTP_get URL ;///////////////////////////////////////////////////////////////////////////////;; |
;;------------------------------------------------------------------------------------------------;; |
;? ;; |
;;------------------------------------------------------------------------------------------------;; |
;> _ ;; |
;;------------------------------------------------------------------------------------------------;; |
;< eax = 0 (error) / buffer ptr ;; |
;;================================================================================================;; |
locals |
hostname dd ? |
pageaddr dd ? |
sockaddr dd ? |
socketnum dd ? |
buffer dd ? |
endl |
|
; split the URL into hostname and pageaddr |
stdcall parse_url, [URL] |
test eax, eax |
jz .error |
mov [hostname], eax |
mov [pageaddr], ebx |
|
; Do we need to use a proxy? |
cmp [proxyAddr], 0 |
jne .proxy_done |
|
; TODO |
.proxy_done: |
|
; Resolve the hostname |
DEBUGF 1, "Resolving hostname\n" |
push esp ; reserve stack place |
push esp ; fourth parameter |
push 0 ; third parameter |
push 0 ; second parameter |
push [hostname] |
call [getaddrinfo] |
pop esi |
test eax, eax |
jnz .error |
|
; getaddrinfo returns addrinfo struct, make the pointer to sockaddr struct |
mov esi, [esi + addrinfo.ai_addr] |
mov [sockaddr], esi |
mov eax, [esi + sockaddr_in.sin_addr] |
test eax, eax |
jz .error |
|
DEBUGF 1, "Server ip=%u.%u.%u.%u\n", \ |
[esi + sockaddr_in.sin_addr]:1, [esi + sockaddr_in.sin_addr + 1]:1, \ |
[esi + sockaddr_in.sin_addr + 2]:1, [esi + sockaddr_in.sin_addr + 3]:1 |
|
mov [esi + sockaddr_in.sin_family], AF_INET4 |
mov [esi + sockaddr_in.sin_port], 80 shl 8 ;;; FIXME |
|
; Connect to the server. |
mcall socket, AF_INET4, SOCK_STREAM, 0 |
test eax, eax |
jz .error |
mov [socketnum], eax |
DEBUGF 1, "Socket: 0x%x\n", eax |
|
mcall connect, [socketnum], [sockaddr], 18 |
test eax, eax |
jnz .error |
DEBUGF 1, "Socket is now connected.\n" |
|
; TODO: free address buffer(s) |
|
; Create the HTTP request. |
invoke mem.alloc, BUFFERSIZE |
test eax, eax |
jz .error |
mov [buffer], eax |
DEBUGF 1, "Buffer has been allocated.\n" |
|
mov dword[eax], 'GET ' |
lea edi, [eax + 4] |
mov esi, [pageaddr] ; TODO: for proxy use http:// and then full URL |
copy_till_zero |
|
mov esi, str_http11 |
mov ecx, str_http11.length |
rep movsb |
|
mov esi, [hostname] |
copy_till_zero |
|
mov esi, str_close |
mov ecx, str_close.length |
rep movsb |
|
mov byte[edi], 0 |
DEBUGF 1, "Request:\n%s", [buffer] |
|
; now send the request |
mov esi, edi |
sub esi, [buffer] ; length |
xor edi, edi ; flags |
|
mcall send, [socketnum], [buffer] |
test eax, eax |
jz .error |
DEBUGF 1, "Request has been sent to server.\n" |
|
; Now that we have sent the request, re-purpose buffer as receive buffer |
mov eax, [buffer] |
push [socketnum] |
popd [eax + http_msg.socket] |
lea esi, [eax + http_msg.data] |
mov [eax + http_msg.flags], 0 |
mov [eax + http_msg.write_ptr], esi |
mov [eax + http_msg.buffer_length], BUFFERSIZE - http_msg.data |
mov [eax + http_msg.chunk_ptr], 0 |
|
mov [eax + http_msg.status], 0 |
mov [eax + http_msg.header_length], 0 |
mov [eax + http_msg.content_length], 0 |
|
ret ; return buffer ptr |
|
.error: |
DEBUGF 1, "Error!\n" |
xor eax, eax ; return 0 = error |
ret |
|
endp |
|
|
|
;;================================================================================================;; |
proc HTTP_process identifier ;////////////////////////////////////////////////////////////////////;; |
;;------------------------------------------------------------------------------------------------;; |
;? ;; |
;;------------------------------------------------------------------------------------------------;; |
;> _ ;; |
;;------------------------------------------------------------------------------------------------;; |
;< eax = -1 (not finished) / 0 finished ;; |
;;================================================================================================;; |
pusha |
mov ebp, [identifier] |
mcall recv, [ebp + http_msg.socket], [ebp + http_msg.write_ptr], \ |
[ebp + http_msg.buffer_length], MSG_DONTWAIT |
cmp eax, 0xffffffff |
je .check_socket |
DEBUGF 1, "Received %u bytes\n", eax |
|
mov edi, [ebp + http_msg.write_ptr] |
add [ebp + http_msg.write_ptr], eax |
sub [ebp + http_msg.buffer_length], eax |
jz .got_all_data |
test [ebp + http_msg.flags], FLAG_GOT_HEADER |
jnz .header_parsed |
|
sub eax, 4 |
jl .no_header |
.scan: |
; scan for end of header (empty line) |
cmp dword[edi], 0x0a0d0a0d ; end of header |
je .end_of_header |
cmp word[edi+2], 0x0a0a |
je .end_of_header |
inc edi |
dec eax |
jnz .scan |
|
.no_header: |
popa |
xor eax, eax |
dec eax |
ret |
|
.end_of_header: |
add edi, 4 - http_msg.data |
sub edi, ebp |
mov [ebp + http_msg.header_length], edi |
or [ebp + http_msg.flags], FLAG_GOT_HEADER |
DEBUGF 1, "Header length: %u\n", edi |
|
; Ok, we have found header: |
cmp dword[ebp + http_msg.data], 'HTTP' |
jne .invalid_header |
cmp dword[ebp + http_msg.data+4], '/1.0' |
je .http_1.0 |
cmp dword[ebp + http_msg.data+4], '/1.1' |
jne .invalid_header |
or [ebp + http_msg.flags], FLAG_HTTP11 |
.http_1.0: |
cmp byte[ebp + http_msg.data+8], ' ' |
jne .invalid_header |
DEBUGF 1, "Header seems valid.\n" |
|
lea esi, [ebp + http_msg.data+9] |
xor eax, eax |
xor ebx, ebx |
mov ecx, 3 |
.statusloop: |
lodsb |
sub al, '0' |
jb .invalid_header |
cmp al, 9 |
ja .invalid_header |
lea ebx, [ebx + 4*ebx] |
shl ebx, 1 |
add ebx, eax |
dec ecx |
jnz .statusloop |
mov [ebp + http_msg.status], ebx |
DEBUGF 1, "Status: %u\n", ebx |
|
; Now, convert all header names to lowercase. |
; This way, it will be much easier to find certain header fields, later on. |
|
lea esi, [ebp + http_msg.data] |
mov ecx, [ebp + http_msg.header_length] |
.need_newline: |
inc esi |
dec ecx |
jz .convert_done |
cmp byte[esi], 10 |
jne .need_newline |
; Ok, we have a newline, a line beginning with space or tabs has no header fields. |
|
inc esi |
dec ecx |
jz .convert_done |
cmp byte[esi], ' ' |
je .need_newline |
cmp byte[esi], 9 ; horizontal tab |
je .need_newline |
jmp .convert_loop |
.next_char: |
inc esi |
dec ecx |
jz .convert_done |
.convert_loop: |
cmp byte[esi], ':' |
je .need_newline |
cmp byte[esi], 'A' |
jb .next_char |
cmp byte[esi], 'Z' |
ja .next_char |
or byte[esi], 0x20 ; convert to lowercase |
jmp .next_char |
.convert_done: |
mov byte[esi-1], 0 |
lea esi, [ebp + http_msg.data] |
DEBUGF 1, "Header names converted to lowercase:\n%s\n", esi |
|
; Check for content-length header field. |
stdcall find_header_field, ebp, str_cl |
test eax, eax |
jz .no_content |
or [ebp + http_msg.flags], FLAG_CONTENT_LENGTH |
|
xor edx, edx |
.cl_loop: |
movzx ebx, byte[eax] |
inc eax |
cmp bl, 10 |
je .cl_ok |
cmp bl, 13 |
je .cl_ok |
cmp bl, ' ' |
je .cl_ok |
sub bl, '0' |
jb .invalid_header |
cmp bl, 9 |
ja .invalid_header |
lea edx, [edx + edx*4] ; edx = edx*10 |
shl edx, 1 ; |
add edx, ebx |
jmp .cl_loop |
|
.cl_ok: |
mov [ebp + http_msg.content_length], edx |
DEBUGF 1, "Content-length: %u\n", edx |
|
; Resize buffer according to content-length. |
mov eax, [ebp + http_msg.header_length] |
add eax, [ebp + http_msg.content_length] |
add eax, http_msg.data |
|
mov ecx, eax |
sub ecx, [ebp + http_msg.write_ptr] |
mov [ebp + http_msg.buffer_length], ecx |
|
invoke mem.realloc, ebp, eax |
or eax, eax |
jz .no_ram |
jmp .header_parsed ; hooray! |
|
.no_content: |
DEBUGF 1, "Content-length not found.\n" |
|
; We didnt find 'content-length', maybe server is using chunked transfer encoding? |
; Try to find 'transfer-encoding' header. |
stdcall find_header_field, ebp, str_te |
test eax, eax |
jz .invalid_header |
|
mov ebx, dword[eax] |
or eax, 0x20202020 |
cmp ebx, 'chun' |
jne .invalid_header |
mov ebx, dword[eax+4] |
or eax, 0x00202020 |
and eax, 0x00ffffff |
cmp ebx, 'ked' |
jne .invalid_header |
|
or [ebp + http_msg.flags], FLAG_CHUNKED |
|
DEBUGF 1, "Transfer type is: chunked\n" |
|
; Set chunk pointer where first chunk should begin. |
mov eax, [ebp + http_msg.header_length] |
add eax, http_msg.data |
mov [ebp + http_msg.chunk_ptr], eax |
|
.header_parsed: |
; If data is chunked, combine chunks into contiguous data if so. |
test [ebp + http_msg.flags], FLAG_CHUNKED |
jz .not_chunked |
|
.chunkloop: |
mov ecx, [ebp + http_msg.write_ptr] |
sub ecx, [ebp + http_msg.chunk_ptr] |
jb .not_finished |
|
mov esi, [ebp + http_msg.chunk_ptr] |
xor ebx, ebx |
.chunk_hexloop: |
lodsb |
sub al, '0' |
jb .chunk_ |
cmp al, 9 |
jbe .chunk_hex |
sub al, 'A' - '0' |
jb .chunk_ |
cmp al, 5 |
jbe .chunk_hex |
sub al, 'a' - 'A' |
cmp al, 5 |
ja .chunk_ |
.chunk_hex: |
shl ebx, 4 |
add bl, al |
jmp .chunk_hexloop |
.chunk_: |
DEBUGF 1, "got chunk of %u bytes\n", ebx |
; If chunk size is 0, all chunks have been received. |
test ebx, ebx |
jz .got_all_data ; last chunk, hooray! FIXME: what if it wasnt a valid hex number??? |
add [ebp + http_msg.chunk_ptr], ebx |
|
; Chunkline ends with a CR, LF or simply LF |
.end_of_chunkline?: ; FIXME: buffer overflow possible! |
cmp al, 10 |
je .end_of_chunkline |
lodsb |
jmp .end_of_chunkline? |
|
.end_of_chunkline: |
; Now move all received data to the left (remove chunk header). |
; Meanwhile, update write_ptr and content_length accordingly. |
mov edi, [ebp + http_msg.chunk_ptr] |
mov ecx, [ebp + http_msg.write_ptr] |
sub ecx, esi |
mov eax, esi |
sub eax, edi |
sub [ebp + http_msg.write_ptr], eax |
add [ebp + http_msg.content_length], ecx |
rep movsb |
jmp .chunkloop |
|
.not_chunked: |
; Check if we got all the data. |
mov eax, [ebp + http_msg.header_length] |
add eax, [ebp + http_msg.content_length] |
cmp eax, [ebp + http_msg.buffer_length] |
je .got_all_data |
|
.not_finished: |
; DEBUGF 1, "Needs more processing...\n" |
popa |
xor eax, eax |
dec eax |
ret |
|
.got_all_data: |
DEBUGF 1, "We got all the data!\n" |
or [ebp + http_msg.flags], FLAG_GOT_DATA |
mcall close, [ebp + http_msg.socket] |
popa |
xor eax, eax |
ret |
|
.check_socket: |
cmp ebx, EWOULDBLOCK |
je .not_finished |
DEBUGF 1, "ERROR: socket error %u\n", ebx |
|
or [ebp + http_msg.flags], FLAG_SOCKET_ERROR |
popa |
xor eax, eax |
ret |
|
.invalid_header: |
DEBUGF 1, "ERROR: invalid header\n" |
or [ebp + http_msg.flags], FLAG_INVALID_HEADER |
popa |
xor eax, eax |
ret |
|
.no_ram: |
DEBUGF 1, "ERROR: out of RAM\n" |
or [ebp + http_msg.flags], FLAG_NO_RAM |
popa |
xor eax, eax |
ret |
|
endp |
|
|
|
;;================================================================================================;; |
proc find_header_field identifier, headername ;///////////////////////////////////////////////////;; |
;;------------------------------------------------------------------------------------------------;; |
;? ;; |
;;------------------------------------------------------------------------------------------------;; |
;> _ ;; |
;;------------------------------------------------------------------------------------------------;; |
;< eax = -1 (error) / 0 ;; |
;;================================================================================================;; |
push ebx ecx edx esi edi |
|
DEBUGF 1, "Find header field: %s\n", [headername] |
|
mov ebx, [identifier] |
lea edx, [ebx + http_msg.data] |
mov ecx, edx |
add ecx, [ebx + http_msg.header_length] |
|
.restart: |
mov esi, [headername] |
mov edi, edx |
.loop: |
cmp edi, ecx |
jae .fail |
lodsb |
scasb |
je .loop |
test al, al |
jz .done? |
.next: |
inc edx |
jmp .restart |
|
.not_done: |
inc edi |
.done?: |
cmp byte[edi-1], ':' |
je .almost_done |
cmp byte[edi-1], ' ' |
je .not_done |
cmp byte[edi-1], 9 ; tab |
je .not_done |
|
jmp .next |
|
.almost_done: ; FIXME: buffer overflow? |
dec edi |
DEBUGF 1, "Found header field\n" |
.spaceloop: |
inc edi |
cmp byte[edi], ' ' |
je .spaceloop |
cmp byte[edi], 9 ; tab |
je .spaceloop |
|
mov eax, edi |
pop edi esi edx ecx ebx |
ret |
|
.fail: |
pop edi esi edx ecx ebx |
xor eax, eax |
ret |
|
endp |
|
|
; internal procedures start here: |
|
|
;;================================================================================================;; |
proc parse_url URL ;//////////////////////////////////////////////////////////////////////////////;; |
;;------------------------------------------------------------------------------------------------;; |
;? ;; |
;;------------------------------------------------------------------------------------------------;; |
;> _ ;; |
;;------------------------------------------------------------------------------------------------;; |
;< eax = -1 (error) / 0 ;; |
;;================================================================================================;; |
|
locals |
urlsize dd ? |
hostname dd ? |
pageaddr dd ? |
endl |
|
DEBUGF 1, "URL: %s\n", [URL] |
|
; remove any leading protocol text |
mov esi, [URL] |
mov ecx, URLMAXLEN |
mov ax, '//' |
.loop1: |
cmp byte[esi], 0 ; end of URL? |
je .url_ok ; yep, so not found |
cmp [esi], ax |
je .skip_proto |
inc esi |
dec ecx |
jnz .loop1 |
|
; URL invalid ! |
xor eax, eax |
ret |
|
.skip_proto: |
inc esi ; skip the two '/' |
inc esi |
mov [URL], esi ; update pointer so it skips protocol |
jmp .loop1 ; we still need to find the length of the URL |
|
.url_ok: |
sub esi, [URL] ; calculate total length of URL |
mov [urlsize], esi |
|
;;; FIXME: urls with no pageaddr are not parsed correctly!! |
|
; now look for page delimiter - it's a '/' character |
mov ecx, esi ; URL length |
mov edi, [URL] |
mov al, '/' |
repne scasb |
dec edi ; return one char, '/' must be part of the pageaddr |
inc ecx ; |
push ecx edi ; remember the pointer and length of pageaddr |
|
mov ecx, edi |
sub ecx, [URL] |
inc ecx ; we will add a 0 byte at the end |
invoke mem.alloc, ecx |
or eax, eax |
jz .no_mem |
|
mov [hostname], eax ; copy hostname to buffer |
mov edi, eax |
mov esi, [URL] |
dec ecx |
rep movsb |
xor al, al |
stosb |
|
mov [pageaddr], null_str ; assume there is no pageaddr |
pop esi ecx |
test ecx, ecx |
jz .no_page |
inc ecx ; we will add a 0 byte at the end |
invoke mem.alloc, ecx |
or eax, eax |
jz .no_mem |
|
mov [pageaddr], eax ; copy pageaddr to buffer |
mov edi, eax |
dec ecx |
rep movsb |
xor al, al |
stosb |
.no_page: |
|
mov eax, [hostname] |
mov ebx, [pageaddr] |
|
DEBUGF 1, "hostname: %s\n", eax |
DEBUGF 1, "pageaddr: %s\n", ebx |
|
ret |
|
.no_mem: |
xor eax, eax |
ret |
|
endp |
|
|
|
|
|
;;================================================================================================;; |
;;////////////////////////////////////////////////////////////////////////////////////////////////;; |
;;================================================================================================;; |
;! Imported functions section ;; |
;;================================================================================================;; |
;;////////////////////////////////////////////////////////////////////////////////////////////////;; |
;;================================================================================================;; |
|
|
align 16 |
@IMPORT: |
|
library \ |
libini, 'libini.obj', \ |
network, 'network.obj' |
|
import libini, \ |
ini.get_str, 'ini_get_str', \ |
ini.get_int, 'ini_get_int' |
|
import network,\ |
getaddrinfo, 'getaddrinfo',\ |
freeaddrinfo, 'freeaddrinfo',\ |
inet_ntoa, 'inet_ntoa' |
|
;;===========================================================================;; |
;;///////////////////////////////////////////////////////////////////////////;; |
;;===========================================================================;; |
;! Exported functions section ;; |
;;===========================================================================;; |
;;///////////////////////////////////////////////////////////////////////////;; |
;;===========================================================================;; |
|
|
align 4 |
@EXPORT: |
export \ |
lib_init , 'lib_init' , \ |
0x00010001 , 'version' , \ |
HTTP_get , 'get' , \ |
find_header_field , 'find_header_field' , \ |
HTTP_process , 'process' |
|
; HTTP_head , 'head' , \ |
; HTTP_post , 'post' , \ |
; HTTP_put , 'put' , \ |
; HTTP_delete , 'delete' , \ |
; HTTP_trace , 'trace' , \ |
; HTTP_connect , 'connect' , \ |
|
|
|
section '.data' data readable writable align 16 |
|
inifile db '/sys/settings/network.ini', 0 |
|
sec_proxy: |
key_proxy db 'proxy', 0 |
key_proxyport db 'port', 0 |
key_user db 'user', 0 |
key_password db 'password', 0 |
|
str_http11 db ' HTTP/1.1', 13, 10, 'Host: ' |
.length = $ - str_http11 |
str_close db 13, 10, 'User-Agent: KolibriOS libHTTP/1.0', 13, 10, 'Connection: Close', 13, 10, 13, 10 |
.length = $ - str_close |
str_proxy_auth db 13, 10, 'Proxy-Authorization: Basic ' |
.length = $ - str_proxy_auth |
|
base64_table db 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' |
db '0123456789+/' |
|
str_cl db 'content-length', 0 |
null_str db 0 |
str_te db 'transfer-encoding', 0 |
|
include_debug_strings |
|
; uninitialized data |
mem.alloc dd ? |
mem.free dd ? |
mem.realloc dd ? |
dll.load dd ? |
|
proxyAddr rb 256 |
proxyUser rb 256 |
proxyPassword rb 256 |
proxyPort dd ? |