122 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			122 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			V
		
	
	
| module vweb
 | |
| 
 | |
| import io
 | |
| import strings
 | |
| import net.http
 | |
| import net.urllib
 | |
| 
 | |
| fn parse_request(mut reader io.BufferedReader) ?http.Request {
 | |
| 	// request line
 | |
| 	mut line := reader.read_line() ?
 | |
| 	method, target, version := parse_request_line(line) ?
 | |
| 
 | |
| 	// headers
 | |
| 	mut header := http.new_header()
 | |
| 	line = reader.read_line() ?
 | |
| 	for line != '' {
 | |
| 		key, value := parse_header(line) ?
 | |
| 		header.add_custom(key, value) ?
 | |
| 		line = reader.read_line() ?
 | |
| 	}
 | |
| 	header.coerce(canonicalize: true)
 | |
| 
 | |
| 	// body
 | |
| 	mut body := []byte{}
 | |
| 	if length := header.get(.content_length) {
 | |
| 		n := length.int()
 | |
| 		if n > 0 {
 | |
| 			body = []byte{len: n}
 | |
| 			mut count := 0
 | |
| 			for count < body.len {
 | |
| 				count += reader.read(mut body[count..]) or { break }
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return http.Request{
 | |
| 		method: method
 | |
| 		url: target.str()
 | |
| 		header: header
 | |
| 		data: body.bytestr()
 | |
| 		version: version
 | |
| 	}
 | |
| }
 | |
| 
 | |
| fn parse_request_line(s string) ?(http.Method, urllib.URL, http.Version) {
 | |
| 	words := s.split(' ')
 | |
| 	if words.len != 3 {
 | |
| 		return error('malformed request line')
 | |
| 	}
 | |
| 	method := http.method_from_str(words[0])
 | |
| 	target := urllib.parse(words[1]) ?
 | |
| 	version := http.version_from_str(words[2])
 | |
| 	if version == .unknown {
 | |
| 		return error('unsupported version')
 | |
| 	}
 | |
| 
 | |
| 	return method, target, version
 | |
| }
 | |
| 
 | |
| fn parse_header(s string) ?(string, string) {
 | |
| 	if !s.contains(':') {
 | |
| 		return error('missing colon in header')
 | |
| 	}
 | |
| 	words := s.split_nth(':', 2)
 | |
| 	// TODO: parse quoted text according to the RFC
 | |
| 	return words[0], words[1].trim_left(' \t')
 | |
| }
 | |
| 
 | |
| // Parse URL encoded key=value&key=value forms
 | |
| fn parse_form(body string) map[string]string {
 | |
| 	words := body.split('&')
 | |
| 	mut form := map[string]string{}
 | |
| 	for word in words {
 | |
| 		kv := word.split_nth('=', 2)
 | |
| 		if kv.len != 2 {
 | |
| 			continue
 | |
| 		}
 | |
| 		key := urllib.query_unescape(kv[0]) or { continue }
 | |
| 		val := urllib.query_unescape(kv[1]) or { continue }
 | |
| 		form[key] = val
 | |
| 	}
 | |
| 	return form
 | |
| 	// }
 | |
| 	// todo: parse form-data and application/json
 | |
| 	// ...
 | |
| }
 | |
| 
 | |
| // Parse the Content-Disposition header of a multipart form
 | |
| // Returns a map of the key="value" pairs
 | |
| // Example: parse_disposition('Content-Disposition: form-data; name="a"; filename="b"') == {'name': 'a', 'filename': 'b'}
 | |
| fn parse_disposition(line string) map[string]string {
 | |
| 	mut data := map[string]string{}
 | |
| 	for word in line.split(';') {
 | |
| 		kv := word.split_nth('=', 2)
 | |
| 		if kv.len != 2 {
 | |
| 			continue
 | |
| 		}
 | |
| 		key, value := kv[0].to_lower().trim_left(' \t'), kv[1].trim_right('\r')
 | |
| 		if value.starts_with('"') && value.ends_with('"') {
 | |
| 			data[key] = value[1..value.len - 1]
 | |
| 		} else {
 | |
| 			data[key] = value
 | |
| 		}
 | |
| 	}
 | |
| 	return data
 | |
| }
 | |
| 
 | |
| [manualfree]
 | |
| fn lines_to_string(len int, lines []string, start int, end int) string {
 | |
| 	mut sb := strings.new_builder(len)
 | |
| 	for i in start .. end {
 | |
| 		sb.writeln(lines[i])
 | |
| 	}
 | |
| 	sb.cut_last(1) // last newline
 | |
| 	if sb.last_n(1) == '\r' {
 | |
| 		sb.cut_last(1)
 | |
| 	}
 | |
| 	res := sb.str()
 | |
| 	unsafe { sb.free() }
 | |
| 	return res
 | |
| }
 |