# PURPOSE: a http server
#
# AUTHOR: Andreas Jaggi <andreas.jaggi@waterwave.ch>
#
# All rights reserved.

.equ OUTPUT, 1

.equ REQUIRED_PERM, 0x124	# = 0444

.equ SENDFILE_SIZE, 4096

.section .data

socket_args:
.long	2,1,6		# AF_INET, SOCK_STREAM, IPPROTO_TCP

local_sockaddr:
.byte 0x00		# AF_INET
.byte 0x02
.byte 0x1f		# port: 8080 (80 = 0x0050)
.byte 0x90
.byte 0x7f		# IP: 127.0.0.1
.byte 0x00
.byte 0x00
.byte 0x01
.long 0x0,0x0		# zero-fill

bind_args:
.long 0
.long local_sockaddr
.long 0x10

listen_args:
.long 0,4

accept_remote_addr: #22 bytes
.long 0,0,0,0,0
.byte 0
accept_remote_addr_len:
.long 22

accept_args:
.long 0,accept_remote_addr,accept_remote_addr_len

recv_args:
.long 0,recv_buffer,RECV_BUFFER_SIZE,0 

shutdown_args:
.long 0,2

CHR_SPACE:
.ascii " "
CHR_NEWLINE:
.ascii "\n"
CHR_SLASH:
.ascii "/"
CHR_QUESTIONMARK:
.ascii "?"
CHR_PERCENT:
.ascii "%"
CHR_DOT:
.ascii "."

INDEX_HTML:
.ascii "index.html\0"


serving_msg:
.ascii "Serving file "
.equ serving_msg_len, 13

resp_200:
.ascii "HTTP/1.0 200 OK\n"
.equ resp_200_len, 16

resp_logo:
.ascii "Server: asm80\n"
.equ resp_logo_len, 14

resp_400:
.ascii "HTTP/1.0 400 Bad request\n"
.equ resp_400_len,25 

resp_403:
.ascii "HTTP/1.0 403 Forbidden\n"
.equ resp_403_len, 23

resp_404: .ascii "HTTP/1.0 404 File not found\n"
.equ resp_404_len, 28

resp_414: .ascii "HTTP/1.0 414 Request URL too long\n"
.equ resp_414_len, 34

resp_501: .ascii "HTTP/1.0 501 Not implemented\n"
.equ resp_501_len, 29


# syscall interrupt
.equ SYSCALL, 0x80

# syscalls
.equ EXIT, 1
.equ READ, 3
.equ WRITE, 4
.equ OPEN, 5
.equ CLOSE, 6
.equ SOCKETCALL, 102
.equ STAT, 106
.equ SENDFILE, 187

# socketcall calls
.equ SYS_SOCKET, 1
.equ SYS_BIND, 2
.equ SYS_LISTEN, 4
.equ SYS_ACCEPT, 5
.equ SYS_SEND, 9
.equ SYS_RECV, 10
.equ SYS_SHUTDOWN, 13

.equ O_RDONLY, 0

.equ EACCES,-13

.equ __S_IFREG, 0x8000
.equ __S_IFDIR, 0x4000


.section .bss
.equ RECV_BUFFER_SIZE, 4096
.lcomm recv_buffer, RECV_BUFFER_SIZE

.equ STAT_BUF_SIZE, 88
.lcomm stat_buf, STAT_BUF_SIZE

.section .text

.globl _start

_start:
					#sock = socket(AF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6)
	movl $SOCKETCALL, %eax		#socketcall
	movl $SYS_SOCKET, %ebx		#SYS_SOCKET
	movl $socket_args, %ecx		#AF_INET,SOCK_STREAM,IPROTO_TCP
	int $SYSCALL

	pushl %eax			#put socket on stack

	cmpl $0, %eax
	je socket_error_end
	movl $0, %ebx
	subl %eax, %ebx
	movl $EXIT, %eax
socket_error_end:

					#bind(sock, local_addr, sizeof(local_addr));
	movl $SOCKETCALL, %eax		#socketcall
	movl $SYS_BIND, %ebx		#SYS_BIND
	movl (%esp), %edx 		#put socket in args
	movl %edx, (bind_args)
	movl $bind_args, %edx		#put local_sockaddr in args
	movl $local_sockaddr, 4(%edx)
	movl $bind_args, %ecx
	int $SYSCALL

	cmpl $0, %eax
	je bind_error_end
	movl $0, %ebx
	subl %eax, %ebx
	movl $EXIT, %eax
	int $SYSCALL
bind_error_end:


					#listen(sock, x?);
	movl $SOCKETCALL, %eax		#socketcall
	movl $SYS_LISTEN, %ebx		#SYS_LISTEN
	movl (%esp), %edx 		#put socket in args
	movl %edx, (listen_args)
	movl $listen_args, %ecx
	int $SYSCALL

	cmpl $0, %eax
	je listen_error_end
	movl $0, %ebx
	subl %eax, %ebx
	movl $EXIT, %eax
	int $SYSCALL
listen_error_end:

	pushl $0xdeadbeaf		#put nonsense on stack
sock_loop:
	addl $4, %esp			#pop stack (e.g. free nonsense or sock2)

					#while ( ( sock2 = accept(sock, remote_host_addr, max_remote_host_addr_len) )  > -1 )
	movl $SOCKETCALL, %eax		#socketcall
	movl $SYS_ACCEPT, %ebx		#SYS_ACCEPT
	movl (%esp), %edx 		#put socket in args
	movl %edx, (accept_args)
	movl $accept_args, %ecx
	int $SYSCALL

	cmpl $1, %eax
	jl end_sock

	pushl %eax			#put sock2 on stack

# disable fork for gdb
#	movl $2, %eax			#fork
#	int $SYSCALL
#
#	cmpl $0, %eax
#	jne sock_loop

sock2_loop:
					#n = recv(sock2, buff, len, flags)	
	movl $SOCKETCALL, %eax		#socketcall
	movl $SYS_RECV, %ebx		#SYS_RECV
	movl (%esp), %edx 		#put sock2 in args
	movl %edx, (recv_args)
	movl $recv_args, %ecx
	int $SYSCALL

	#cmpl $1, %eax
	cmpl $14, %eax			#close connection on error or when < 14 bytes received
	jl end_sock2

	movl $recv_buffer, %ebx		# %ebx points to buffer
	addl $recv_buffer, %eax		# %eax now points 1 byte behind buffer

					
					# search the first space in the first line of the request (where the GET or POST is located)
					# in order to extract the requested filename
					# the line should look like one of these:
					# POST / HTTP/1.x
					# GET / HTTP/1.x
					# POST /foo HTTP/1.x
					# GET /foo HTTP/1.x
					# etc.

rq_space_search_loop:
	incl %ebx
	cmpl %eax, %ebx			# when end of buffer is reached, send 400
	je quit_with_400
	movb (%ebx), %dl
	cmpb (CHR_NEWLINE), %dl		# when there's no space in the first line, send 414
	je quit_with_414
	cmpb (CHR_SPACE), %dl
	jne rq_space_search_loop
	incl %ebx			# %ebx now points to begin of REQUEST_STRING

					# search the second space
	movl %ebx, %ecx
	decl %ecx
rq_qm_search_loop:
	incl %ecx
	cmpl %eax, %ecx			# when end of buffer is reached, send 414
	je quit_with_414
	movb (%ecx), %dl
	cmpb (CHR_NEWLINE), %dl		# when there's a linebreak before the questionmark (or space), send 400
	je quit_with_400
	cmpb (CHR_SPACE), %dl		# when there's a space before the questionmark, treat it as questionmark (e.g. assume that there is no questionmark)
	je end_rq_qm_search_loop
	cmpb (CHR_QUESTIONMARK), %dl
	jne rq_qm_search_loop
end_rq_qm_search_loop:
					# %ecx now points 1 byte behind REQUEST_STRING (XXX: this is wrong when there is a SPACE in the request string!!!)
	
	pushl %eax			# backup registers
	pushl %ebx
	pushl %ecx
	pushl %edx

					# write "Serving file xxx\n" on output
	movl $WRITE, %eax
	movl $OUTPUT, %ebx
	movl $serving_msg, %ecx
	movl $serving_msg_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl $OUTPUT, %ebx

	movl 8(%esp), %ecx

	movl 4(%esp), %edx
	subl 8(%esp), %edx

	int $SYSCALL

	movl $WRITE, %eax
	movl $OUTPUT, %ebx
	movl $CHR_NEWLINE, %ecx
	movl $1, %edx
	int $SYSCALL

					# restore registers:
	popl %edx
	popl %ecx			# %ecx points 1 byte behind REQUEST_STRING
	popl %ebx			# %ebx points to the begin of REQUEST_STRING
	popl %eax			# %eax points 1 byte behind buffer

	movb $0, (%ecx)			# NULL-terminate REQUEST_STRING

remove_leading_slash_loop:
	incl %ebx			# get rid of the leading slash
	cmpl %ebx, %eax			# if end of buffer reached (e.g. some jerk sending only slashes),
	je end_sock2			# close connection
	movb (%ebx), %dl
	cmpb (CHR_SLASH), %dl
	je remove_leading_slash_loop

	cmpl %ecx, %ebx			# if no filename given (/) serve index.html
	jge default_index_html

	movl %ebx, %esi			# src pointer to REQUEST_STRING
	movl %ebx, %edi			# dst pointer to REQUEST_STRING (yes, we do in-place conversion)

url_copy_loop:
	movl $0, %edx
	movb (%esi), %dl		# get next char from REQUEST_STRING

	cmpb %dl, (CHR_PERCENT)		# urldecode if % found
	jne end_urldecode_chr
	movl $0, %edx
	incl %esi
	movb (%esi), %dl
	cmpb $48, %dl
	jl quit_with_400
	cmpb $57, %dl
	jg end_decode_09
	subl $48, %edx			# 0-9
	jmp calc_second_byte
end_decode_09:
	cmpb $65, %dl
	jl quit_with_400
	cmpb $70, %dl
	jg end_decode_AZ
	subl $55, %edx			# A-Z
	jmp calc_second_byte
end_decode_AZ:
	cmpb $97, %dl
	jl quit_with_400
	cmpb $102, %dl
	jg quit_with_400
	subl $87, %edx			# a-z
calc_second_byte:
	movl $16, %eax
	imull %edx, %eax

	movl $0, %edx
	incl %esi
	movb (%esi), %dl
	cmpb $48, %dl
	jl quit_with_400
	cmpb $57, %dl
	jg end_decode2_09
	subl $48, %edx			# 0-9
	jmp end_decode
end_decode2_09:
	cmpb $65, %dl
	jl quit_with_400
	cmpb $70, %dl
	jg end_decode2_AZ
	subl $55, %edx			# A-Z
	jmp end_decode
end_decode2_AZ:
	cmpb $97, %dl
	jl quit_with_400
	cmpb $102, %dl
	jg quit_with_400
	subl $87, %edx			# a-z
end_decode:
	addl %edx, %eax
	movl %eax, %edx
end_urldecode_chr:

	movb %dl, (%edi)		# write 'translated' char

	incl %esi			# increase src pointer
	incl %edi			# increase dst pointer

	cmpl %ecx, %esi			# check REQUEST_STRING boundary
	jl url_copy_loop		# treat next char of REQUEST_STRING

end_url_copy:
	movl $0, (%edi)			# NULL-terminate REQUEST_STRING (for stat)


	movl %ebx, %esi			# search for '..' and return 403 if found
double_dot_search_loop:
	movb (%esi), %dl
	cmpb %dl, (CHR_DOT)		# first dot found
	je first_dot_found

	incl %esi

	cmpl %ecx, %esi
	jge end_double_dot_search_loop
	jmp double_dot_search_loop
first_dot_found:
	movb (%esi), %dl
	cmpb %dl, (CHR_DOT)		# second dot found
	jne double_dot_search_loop
	jmp quit_with_403		# -> 403
end_double_dot_search_loop:

					# TODO: sanity check of REQUEST_STRING

	
	cmpl %ebx, %ecx			# if no filename given (/) serve index.html
	jg default_index_html_end
default_index_html:
	movl $INDEX_HTML, %ebx
	jmp load_file
default_index_html_end:


load_file:				# assumes pointer to filename in %ebx, null-terminated

	movl $STAT, %eax		# stat file
	movl $stat_buf, %ecx
	int $SYSCALL

	cmpl $EACCES, %eax		# when stat gives EACCES -> 403
	je quit_with_403
	cmpl $0,%eax			# all other stat errors -> 404
	jl quit_with_404

	movl $stat_buf, %ecx
	movl 8(%ecx), %eax		# get mode bits from stat structure
					# stat struct:
					#	st_dev, 32bits ?
					#	st_ino, 32bits ?
					#	st_mode, 32bits ?
					#	...

	movl %eax, %ecx			# if file has not the required permissions -> 403
	andl $REQUIRED_PERM, %ecx
	cmpl $REQUIRED_PERM, %ecx
	jne quit_with_403

	movl %eax, %ecx			# if file is directory, call serve_dir
	andl $__S_IFDIR, %ecx
	cmpl $__S_IFDIR, %ecx
	je serve_dir

	movl %eax, %ecx			# if file is not a regular file -> 403
	andl $__S_IFREG, %ecx
	cmpl $__S_IFREG, %ecx
	jne quit_with_403

					# open file (filename is in %ebx)
	movl $OPEN, %eax
	movl $O_RDONLY, %ecx
	movl $0777, %edx
	int $SYSCALL

	cmpl $0, %eax
	jl quit_with_404		# TODO: is 404 the appropriate error?

	pushl %eax			# put fp on stack

	movl $WRITE, %eax		# send 200 response
	movl 4(%esp), %ebx		# load sock2 from stack
	movl $resp_200, %ecx
	movl $resp_200_len, %edx
	int $SYSCALL

	movl $WRITE, %eax		# send Server: asm80
	movl 4(%esp), %ebx		# load sock2 from stack
	movl $resp_logo, %ecx
	movl $resp_logo_len, %edx
	int $SYSCALL

	movl $WRITE, %eax		# send newline to end header
	movl 4(%esp), %ebx		# load sock2 from stack
	movl $CHR_NEWLINE, %ecx
	movl $1, %edx
	int $SYSCALL

sendfile_loop:
	movl $SENDFILE, %eax
	movl 4(%esp), %ebx		# load sock2 from stack
	movl (%esp), %ecx		# load fp from stack
	movl $0, %edx
	movl $SENDFILE_SIZE, %esi
	int $SYSCALL

	cmpl $0, %eax
	jl close_file

	cmpl $SENDFILE_SIZE, %eax
	je sendfile_loop

close_file:
	popl %ebx			# restore stack (free fp)
	movl $CLOSE, %eax
	int $SYSCALL
	jmp end_sock2

	jmp end_serve_dir
serve_dir:
	# TODO: implement this
	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_501, %ecx
	movl $resp_501_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_logo, %ecx
	movl $resp_logo_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $CHR_NEWLINE, %ecx
	movl $1, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_501, %ecx
	movl $resp_501_len, %edx
	int $SYSCALL

	jmp end_sock2
end_serve_dir:

	jmp end_quit_with_400
quit_with_400:
	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_400, %ecx
	movl $resp_400_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_logo, %ecx
	movl $resp_logo_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $CHR_NEWLINE, %ecx
	movl $1, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_400, %ecx
	movl $resp_400_len, %edx
	int $SYSCALL
end_quit_with_400:

	jmp end_quit_with_403
quit_with_403:
	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_403, %ecx
	movl $resp_403_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_logo, %ecx
	movl $resp_logo_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $CHR_NEWLINE, %ecx
	movl $1, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_403, %ecx
	movl $resp_403_len, %edx
	int $SYSCALL

	jmp end_sock2
end_quit_with_403:

	jmp end_quit_with_404
quit_with_404:
	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_404, %ecx
	movl $resp_404_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_logo, %ecx
	movl $resp_logo_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $CHR_NEWLINE, %ecx
	movl $1, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_404, %ecx
	movl $resp_404_len, %edx
	int $SYSCALL

	jmp end_sock2
end_quit_with_404:

	jmp end_quit_with_414
quit_with_414:
	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_414, %ecx
	movl $resp_414_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_logo, %ecx
	movl $resp_logo_len, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $CHR_NEWLINE, %ecx
	movl $1, %edx
	int $SYSCALL

	movl $WRITE, %eax
	movl (%esp), %ebx		# load sock2 from stack
	movl $resp_414, %ecx
	movl $resp_414_len, %edx
	int $SYSCALL

	jmp end_sock2
end_quit_with_414:

end_sock2:
					#shutdown(sock2, SHUT_RDWR)
	movl $SOCKETCALL, %eax		#socketcall
	movl $SYS_SHUTDOWN, %ebx	#SYS_SHUTDOWN
	movl (%esp), %edx 		#put sock2 in args
	movl %edx, (shutdown_args)
	movl $shutdown_args, %ecx
	int $SYSCALL

					#close(sock2)
	movl $CLOSE, %eax		#close
	movl (%esp), %ebx 		#put sock2 in %ebx
	int $SYSCALL

	jmp sock_loop

end_sock:
					#shutdown(sock, SHUT_RDWR)
	movl $SOCKETCALL, %eax		#socketcall
	movl $SYS_SHUTDOWN, %ebx	#SYS_SHUTDOWN
	movl (%esp), %edx 		#put sock in args
	movl %edx, (shutdown_args)
	movl $shutdown_args, %ecx
	int $SYSCALL

					#close(sock)
	movl $CLOSE, %eax		#close
	movl (%esp), %ebx 		#put sock in %ebx
	int $SYSCALL

	addl $4, %esp			# restore stack (free sock)


	movl $EXIT, %eax		#exit(0)
	movl $0, %ebx
	int $SYSCALL
