done
This commit is contained in:
314
server.s
Normal file
314
server.s
Normal file
@@ -0,0 +1,314 @@
|
||||
# HTTP GET/POST concurrent webserver targeting Linux ABI for x86_64 processors
|
||||
# This code is public domain
|
||||
# Author: xamidev
|
||||
|
||||
.intel_syntax noprefix
|
||||
.global _start
|
||||
|
||||
.equ AF_INET, 2
|
||||
.equ SOCK_STREAM, 1
|
||||
.equ NULL, 0
|
||||
.equ O_RDONLY, 0
|
||||
.equ O_WRONLY, 1
|
||||
.equ O_CREAT, 64
|
||||
|
||||
.section .text
|
||||
|
||||
_start:
|
||||
# write(unsigned int fd, char* buf, size_t count)
|
||||
mov rax, 1
|
||||
mov rdi, 1
|
||||
lea rsi, [rip + str_welcome]
|
||||
mov rdx, 24
|
||||
syscall
|
||||
|
||||
# socket(AF_INET, SOCK_STREAM, NULL)
|
||||
mov rax, 41
|
||||
mov rdi, AF_INET
|
||||
mov rsi, SOCK_STREAM
|
||||
mov rdx, NULL
|
||||
syscall
|
||||
|
||||
# Save sockfd somewhere
|
||||
mov r8, rax
|
||||
|
||||
# bind(int sockfd, struct sockaddr_in*, int addrlen)
|
||||
mov rax, 49
|
||||
mov rdi, r8
|
||||
lea rsi, [rip + sockaddr_in]
|
||||
mov rdx, 16
|
||||
syscall
|
||||
|
||||
# listen(int sockfd, int backlog)
|
||||
mov rax, 50
|
||||
mov rdi, r8
|
||||
mov rsi, 0
|
||||
syscall
|
||||
|
||||
.accept_incoming_connections:
|
||||
# accept(int sockfd, struct sockaddr_in*, int* addrlen)
|
||||
mov rax, 43
|
||||
mov rdi, r8
|
||||
mov rsi, NULL
|
||||
mov rdx, NULL
|
||||
syscall
|
||||
|
||||
# Save sockfd to close it later
|
||||
mov r14, rax
|
||||
|
||||
# fork() -> new child process for incoming connection; PID in rax
|
||||
mov rax, 57
|
||||
syscall
|
||||
|
||||
# If return value is zero, we're in the child process: we can process the connection
|
||||
# If return value is non-zero, we're in the parent process: we have to get back to accepting incoming connections
|
||||
|
||||
test rax, rax
|
||||
jz .continue
|
||||
jmp .close_actual_sockfd
|
||||
|
||||
.close_actual_sockfd:
|
||||
# close(unsigned int fd)
|
||||
mov rax, 3
|
||||
mov rdi, r14
|
||||
syscall
|
||||
jmp .accept_incoming_connections
|
||||
|
||||
.continue:
|
||||
# close(unsigned int fd)
|
||||
mov rax, 3
|
||||
mov rdi, r8
|
||||
syscall
|
||||
|
||||
# read(unsigned int fd, char* buf, size_t count)
|
||||
mov rax, 0
|
||||
mov rdi, r14
|
||||
lea rsi, [rip + request_buf]
|
||||
mov rdx, 1024
|
||||
syscall
|
||||
|
||||
# Save read bytes (full request size)
|
||||
mov r13, rax
|
||||
|
||||
.parse_http_path:
|
||||
# Parse the HTTP request
|
||||
# When one space is found, extract bytes until next space
|
||||
lea rsi, [rip + request_buf]
|
||||
xor rcx, rcx
|
||||
.find_start:
|
||||
mov al, byte [rsi + rcx]
|
||||
cmp al, ' '
|
||||
je .path_start
|
||||
inc rcx
|
||||
jmp .find_start
|
||||
|
||||
.path_start:
|
||||
inc rcx
|
||||
lea rdi, [rsi+rcx]
|
||||
xor rdx, rdx
|
||||
.path_loop:
|
||||
mov al, byte [rdi + rdx]
|
||||
cmp al, ' '
|
||||
je .path_done
|
||||
mov byte [path_buf + rdx], al
|
||||
inc rdx
|
||||
jmp .path_loop
|
||||
.path_done:
|
||||
|
||||
# Detect if request is GET or POST (first char is G: GET, else POST)
|
||||
.parse_get_post:
|
||||
lea rsi, [rip + request_buf]
|
||||
mov al, byte [rsi]
|
||||
cmp al, 69 # 'E' (2nd letter, rsi+1)
|
||||
je .get_request
|
||||
jmp .post_request
|
||||
|
||||
.post_request:
|
||||
# open(const char* filename, int flags, int mode)
|
||||
mov rax, 2
|
||||
lea rdi, [path_buf+1]
|
||||
mov rsi, O_WRONLY | O_CREAT # O_RDONLY for GET requests
|
||||
mov rdx, 0x1FF # All permissions
|
||||
syscall
|
||||
|
||||
# Save fd
|
||||
mov r10, rax
|
||||
|
||||
.parse_request_content:
|
||||
# When we find \r\n\r\n we know that the content follows.
|
||||
# request_buf already contains full request.
|
||||
lea rsi, [rip + request_buf]
|
||||
xor rcx, rcx
|
||||
|
||||
.find_body:
|
||||
mov al, byte [rsi+rcx]
|
||||
cmp al, 13 # \r
|
||||
jne .next
|
||||
mov al, byte [rsi+rcx+1]
|
||||
cmp al, 10 # \n
|
||||
jne .next
|
||||
mov al, byte [rsi+rcx+2]
|
||||
cmp al, 13
|
||||
jne .next
|
||||
mov al, byte[rsi+rcx+3]
|
||||
cmp al, 10
|
||||
jne .next
|
||||
|
||||
add rcx, 4
|
||||
jmp .body_found
|
||||
|
||||
.next:
|
||||
# Threshold to avoid infinite looping if request is malformed
|
||||
inc rcx
|
||||
cmp rcx, 512
|
||||
jl .find_body
|
||||
|
||||
jmp .error_exit
|
||||
|
||||
.body_found:
|
||||
lea rdi, [rsi+rcx]
|
||||
lea rsi, [rip+request_content_buf]
|
||||
mov rbx, r13
|
||||
sub rbx, rcx
|
||||
mov r13, rbx
|
||||
|
||||
# Byte amount of request content is now in r13
|
||||
# Now we copy this to a buffer
|
||||
xor rcx, rcx
|
||||
|
||||
.content_copy_loop:
|
||||
cmp rcx, r13
|
||||
jge .done_copy
|
||||
|
||||
mov al, byte [rdi+rcx]
|
||||
mov byte [rsi+rcx], al
|
||||
inc rcx
|
||||
jmp .content_copy_loop
|
||||
.done_copy:
|
||||
dec r13
|
||||
# Byte length in r13 (decrement cause of null-byte)
|
||||
# Content in request_content_buf
|
||||
|
||||
# Here, as we have a POST request, we will have to write to filename instead of reading from it.
|
||||
|
||||
# write(unsigned int fd, const char* buf, size_t count)
|
||||
mov rax, 1
|
||||
mov rdi, r10
|
||||
lea rsi, [rip + request_content_buf+1] # ADDRESS OF REQUEST CONTENT BUFFER
|
||||
mov rdx, r13 # AMOUNT OF BYTES IN REQUEST CONTENT
|
||||
syscall
|
||||
|
||||
# Save read bytes
|
||||
mov r15, rax
|
||||
|
||||
# close(unsigned int fd)
|
||||
mov rax, 3
|
||||
mov rdi, r10
|
||||
syscall
|
||||
|
||||
# We simply return a 200 OK here
|
||||
|
||||
# write(unsigned int fd, const char* buf, size_t count)
|
||||
mov rax, 1
|
||||
mov rdi, r14
|
||||
lea rsi, [rip + response]
|
||||
mov rdx, 19
|
||||
syscall
|
||||
|
||||
# close(unsigned int fd)
|
||||
mov rax, 3
|
||||
mov rdi, r14
|
||||
syscall
|
||||
|
||||
jmp .normal_exit
|
||||
|
||||
.get_request:
|
||||
# open(const char* filename, int flags, int mode)
|
||||
mov rax, 2
|
||||
lea rdi, [path_buf+1]
|
||||
mov rsi, O_RDONLY
|
||||
mov rdx, NULL # who cares?
|
||||
syscall
|
||||
|
||||
# Save fd
|
||||
mov r10, rax
|
||||
|
||||
# read(unsigned int fd, char* buf, size_t count)
|
||||
mov rax, 0
|
||||
mov rdi, r10
|
||||
lea rsi, [rip + file_buf]
|
||||
mov rdx, 1024
|
||||
syscall
|
||||
|
||||
# Save read bytes
|
||||
mov r15, rax
|
||||
|
||||
# close(unsigned int fd)
|
||||
mov rax, 3
|
||||
mov rdi, r10
|
||||
syscall
|
||||
|
||||
# write(unsigned int fd, const char* buf, size_t count)
|
||||
mov rax, 1
|
||||
mov rdi, r14
|
||||
lea rsi, [rip + response]
|
||||
mov rdx, 19
|
||||
syscall
|
||||
|
||||
# write(unsigned int fd, const char* buf, size_t count)
|
||||
mov rax, 1
|
||||
mov rdi, r14
|
||||
lea rsi, [rip + file_buf]
|
||||
mov rdx, r15
|
||||
syscall
|
||||
|
||||
# close(unsigned int fd)
|
||||
mov rax, 3
|
||||
mov rdi, r14
|
||||
syscall
|
||||
|
||||
.normal_exit:
|
||||
# exit(0)
|
||||
mov rax, 60
|
||||
mov rdi, 0
|
||||
syscall
|
||||
|
||||
.error_exit:
|
||||
mov rax, 60
|
||||
mov rdi, -1
|
||||
syscall
|
||||
|
||||
.section .data
|
||||
|
||||
response:
|
||||
.asciz "HTTP/1.0 200 OK\r\n\r\n"
|
||||
|
||||
str_welcome:
|
||||
.asciz "Starting HTTP server...\n"
|
||||
|
||||
# Total size: 16 bytes
|
||||
sockaddr_in:
|
||||
.word AF_INET
|
||||
# Port 80 = 0x0050 (unsigned short, 2 bytes)
|
||||
# Big endian: bytes are in reverse order
|
||||
.word 0x5000
|
||||
# 32-bits of zeroes: 0.0.0.0
|
||||
.int 0x00000000
|
||||
.zero 8
|
||||
|
||||
.section .bss
|
||||
|
||||
path_buf:
|
||||
.zero 64
|
||||
|
||||
request_buf:
|
||||
.zero 1024
|
||||
|
||||
file_buf:
|
||||
.zero 1024
|
||||
|
||||
stat_buf:
|
||||
.zero 1024
|
||||
|
||||
request_content_buf:
|
||||
.zero 1024
|
||||
Reference in New Issue
Block a user