├── README.md └── webserver.awk /README.md: -------------------------------------------------------------------------------- 1 | # Simple webserver written in gawk 2 | 3 | A simple webserver written in GNU awk, that supports directory listing and download of files from the directory where it is launched. 4 | 5 | It is born as an experiment, to demonstrate the power of the awk language. 6 | 7 | # Usage 8 | 9 | The script must be executed through a TCP wrapper. 10 | 11 | I use this little shell script that requires socat: 12 | ``` 13 | while [ 1 ]; do 14 | socat TCP-LISTEN:8888,reuseaddr EXEC:"gawk -f webserver.awk" 15 | sleep 1 16 | done 17 | ``` 18 | 19 | You can then connect to the local 8888 port with your browser. Enjoy! 20 | -------------------------------------------------------------------------------- /webserver.awk: -------------------------------------------------------------------------------- 1 | @load "filefuncs" 2 | 3 | BEGIN { 4 | 5 | # 6 | # HTTP separates lines by "\r\n" 7 | # 8 | 9 | RS = "\r\n" 10 | ORS = "\r\n" 11 | 12 | # 13 | # Finite machine statuses 14 | # 15 | 16 | status_request = 1 17 | status_headers = 2 18 | status_file_check = 3 19 | status_success = 4 20 | 21 | current_status = status_request 22 | 23 | # 24 | # Response codes 25 | # 26 | 27 | code_ok = "200 Ok" 28 | code_bad_request = "400 Bad Request" 29 | code_file_not_found = "404 File Not Found" 30 | 31 | response_code = code_bad_request 32 | 33 | # 34 | # Global variables 35 | # 36 | 37 | request_method = "" 38 | request_filename = "" 39 | request_http_version = "" 40 | 41 | # request_headers[] 42 | # response_headers[] 43 | 44 | response_body = "" 45 | 46 | } 47 | 48 | current_status == status_request && 49 | /GET [^ ]+ HTTP/ { 50 | 51 | # 52 | # Read request line 53 | # 54 | 55 | request_method = $1 56 | 57 | request_filename = "./" $2 # prepend ./ for local file 58 | 59 | gsub(/\/\//, "/", request_filename) # replace double slashes 60 | gsub(/\/\.\./, "/", request_filename) # avoid directory trasversal 61 | 62 | request_http_version = $3 63 | 64 | current_status = status_headers 65 | 66 | next 67 | 68 | } 69 | 70 | current_status == status_request && 71 | $0 !~ /GET [^ ]+ HTTP/ { 72 | 73 | # 74 | # Invalid request 75 | # 76 | 77 | response_code = code_bad_request 78 | response_body = response_code 79 | 80 | exit 1 81 | 82 | } 83 | 84 | current_status == status_headers && 85 | /[^:]+: .+/ { 86 | 87 | # 88 | # Read a single header 89 | # 90 | 91 | split($0, header, ": ") 92 | 93 | request_headers[header[1]] = header[2] 94 | 95 | next 96 | 97 | } 98 | 99 | current_status == status_headers && 100 | /^$/ { 101 | 102 | # 103 | # End of headers 104 | # 105 | 106 | current_status = status_file_check 107 | 108 | } 109 | 110 | current_status == status_headers { 111 | 112 | # 113 | # Invalid request header 114 | # 115 | 116 | response_code = code_bad_request 117 | response_body = response_code 118 | 119 | exit 1 120 | 121 | } 122 | 123 | current_status == status_file_check { 124 | 125 | # 126 | # Get info about requested file and 127 | # generate directory listing or file content 128 | # 129 | 130 | ret = stat(request_filename, stat_info) 131 | 132 | if (ret < 0) { 133 | response_code = code_file_not_found 134 | response_body = response_code 135 | exit 1 136 | } 137 | 138 | if (stat_info["type"] == "directory") { 139 | 140 | # Directory listing 141 | 142 | response_body = "
"
143 | 		
144 | 		response_body = response_body "Index of " request_filename "\n"
145 | 
146 | 		cmd = "ls -plh " request_filename
147 | 
148 | 		prev_RS = RS		
149 | 		RS = "\n"
150 | 
151 | 		while ( ( cmd | getline line ) > 0 ) {
152 | 
153 | 			split(line, fields)
154 | 			
155 | 			response_body = response_body fields[5] "\t" fields[9] "\n"
156 | 		}
157 | 		
158 | 		close(cmd)
159 | 
160 | 		RS = prev_RS
161 | 
162 | 		response_body = response_body "
" 163 | 164 | response_headers["Content-Length"] = length(response_body) "" 165 | 166 | } 167 | 168 | else if (stat_info["type"] == "file") { 169 | 170 | # File content 171 | 172 | response_headers["Content-Length"] = stat_info["size"] "" 173 | 174 | prev_RS = RS 175 | RS = "^$" 176 | 177 | cmd = "cat " request_filename 178 | 179 | cmd | getline response_body 180 | 181 | close(cmd) 182 | 183 | RS = prev_RS 184 | 185 | } 186 | 187 | else { 188 | respose_code = code_file_not_found 189 | response_body = response_code 190 | exit 1 191 | } 192 | 193 | current_status = status_success 194 | response_code = code_ok 195 | 196 | exit 0 197 | 198 | } 199 | 200 | END { 201 | 202 | # 203 | # Send back response 204 | # 205 | 206 | print "HTTP/1.1 " response_code 207 | 208 | for (header_name in response_headers) { 209 | print header_name ": " request_headers[header_name] 210 | } 211 | 212 | print 213 | 214 | # Response Body 215 | 216 | ORS="" 217 | 218 | print response_body 219 | 220 | } 221 | --------------------------------------------------------------------------------