diff --git a/vlib/net/http/request.v b/vlib/net/http/request.v index 63f03fac6d..83f7c6837b 100644 --- a/vlib/net/http/request.v +++ b/vlib/net/http/request.v @@ -230,6 +230,11 @@ fn parse_request_line(s string) ?(Method, urllib.URL, Version) { } // Parse URL encoded key=value&key=value forms +// +// FIXME: Some servers can require the +// parameter in a specific order. +// +// a possible solution is to use the a list of QueryValue pub fn parse_form(body string) map[string]string { words := body.split('&') mut form := map[string]string{} diff --git a/vlib/net/urllib/urllib.v b/vlib/net/urllib/urllib.v index ea645618cc..e9a53a6078 100644 --- a/vlib/net/urllib/urllib.v +++ b/vlib/net/urllib/urllib.v @@ -835,28 +835,32 @@ fn parse_query_values(mut m Values, query string) ?bool { } // encode encodes the values into ``URL encoded'' form -// ('bar=baz&foo=quux') sorted by key. +// ('bar=baz&foo=quux'). +// The syntx of the query string is specified in the +// RFC173 https://datatracker.ietf.org/doc/html/rfc1738 +// +// HTTP grammar +// +// httpurl = "http://" hostport [ "/" hpath [ "?" search ]] +// hpath = hsegment *[ "/" hsegment ] +// hsegment = *[ uchar | ";" | ":" | "@" | "&" | "=" ] +// search = *[ uchar | ";" | ":" | "@" | "&" | "=" ] pub fn (v Values) encode() string { if v.len == 0 { return '' } mut buf := strings.new_builder(200) - mut keys := []string{} - for k, _ in v.data { - keys << k - } - keys.sort() - for k in keys { - vs := v.data[k] - key_kscaped := query_escape(k) - for _, val in vs.data { - if buf.len > 0 { - buf.write_string('&') - } - buf.write_string(key_kscaped) - buf.write_string('=') - buf.write_string(query_escape(val)) + for qvalue in v.data { + key_kscaped := query_escape(qvalue.key) + if buf.len > 0 { + buf.write_string('&') } + buf.write_string(key_kscaped) + if qvalue.value == '' { + continue + } + buf.write_string('=') + buf.write_string(query_escape(qvalue.value)) } return buf.str() } diff --git a/vlib/net/urllib/urllib_test.v b/vlib/net/urllib/urllib_test.v index 2b41328bf6..605c6ae7c8 100644 --- a/vlib/net/urllib/urllib_test.v +++ b/vlib/net/urllib/urllib_test.v @@ -40,8 +40,14 @@ fn test_parse_query() ? { q2 := urllib.parse_query('format="%l:+%c+%t"') ? // dump(q1) // dump(q2) - assert q1.data['format'].data == ['"%l: %c %t"'] - assert q2.data['format'].data == ['"%l: %c %t"'] + assert q1.get('format') == '"%l: %c %t"' + assert q2.get('format') == '"%l: %c %t"' +} + +fn test_parse_query_orders() ? { + query_one := urllib.parse_query('https://someapi.com/endpoint?gamma=zalibaba&tau=1&alpha=alibaba&signature=alibaba123') ? + qvalues := query_one.values() + assert qvalues == ['zalibaba', '1', 'alibaba', 'alibaba123'] } fn test_parse_missing_host() ? { @@ -49,3 +55,46 @@ fn test_parse_missing_host() ? { url := urllib.parse('http:///') ? assert url.str() == 'http://///' } + +// testing the case where the key as a null value +// e.g ?key= +fn test_parse_none_value() ? { + query_one := urllib.parse_query('gamma=zalibaba&tau=1&alpha=alibaba&signature=') ? + qvalues := query_one.values() + qvalues_map := query_one.to_map() + assert qvalues == ['zalibaba', '1', 'alibaba'] + assert qvalues_map == { + 'gamma': ['zalibaba'] + 'tau': ['1'] + 'alpha': ['alibaba'] + 'signature': [''] + } +} + +// testing the case where the query as empity value +// e.g https://www.vlang.dev?alibaba +fn test_parse_empty_query_one() ? { + query_str := 'alibaba' + query_one := urllib.parse_query(query_str) ? + qvalues := query_one.values() + qvalues_map := query_one.to_map() + query_encode := query_one.encode() + assert qvalues == [] + assert qvalues_map == { + 'alibaba': [''] + } + assert query_str == query_encode +} + +// testing the case where the query as empity value +// e.g https://www.vlang.dev? +fn test_parse_empty_query_two() ? { + query_str := '' + query_one := urllib.parse_query(query_str) ? + qvalues := query_one.values() + qvalues_map := query_one.to_map() + query_encode := query_one.encode() + assert qvalues == [] + assert qvalues_map == {} + assert query_str == query_encode +} diff --git a/vlib/net/urllib/values.v b/vlib/net/urllib/values.v index 9e66ba2cd6..19336bd947 100644 --- a/vlib/net/urllib/values.v +++ b/vlib/net/urllib/values.v @@ -3,14 +3,15 @@ // that can be found in the LICENSE file. module urllib -struct Value { +struct QueryValue { pub mut: - data []string + key string + value string } struct Values { pub mut: - data map[string]Value + data []QueryValue len int } @@ -20,17 +21,10 @@ pub mut: // values.encode() will return the encoded data pub fn new_values() Values { return Values{ - data: map[string]Value{} + data: []QueryValue{} } } -// Currently you will need to use all()[key].data -// once map[string][]string is implemented -// this will be fixed -pub fn (v &Value) all() []string { - return v.data -} - // get gets the first value associated with the given key. // If there are no values associated with the key, get returns // a empty string. @@ -38,11 +32,12 @@ pub fn (v &Values) get(key string) string { if v.data.len == 0 { return '' } - vs := v.data[key] - if vs.data.len == 0 { - return '' + for qvalue in v.data { + if qvalue.key == key { + return qvalue.value + } } - return vs.data[0] + return '' } // get_all gets the all the values associated with the given key. @@ -52,36 +47,68 @@ pub fn (v &Values) get_all(key string) []string { if v.data.len == 0 { return [] } - vs := v.data[key] - if vs.data.len == 0 { - return [] + mut values := []string{} + for qvalue in v.data { + if qvalue.key == key { + values << qvalue.value + } } - return vs.data + return values } // set sets the key to value. It replaces any existing // values. pub fn (mut v Values) set(key string, value string) { - mut a := v.data[key] - a.data = [value] - v.data[key] = a - v.len = v.data.len + // A query string can contains several + // duplicate, so we need to make sure that we + // cover all the edge case. + for mut qvalue in v.data { + qvalue.value = value + } } // add adds the value to key. It appends to any existing // values associated with key. pub fn (mut v Values) add(key string, value string) { - mut a := v.data[key] - if a.data.len == 0 { - a.data = [] + v.data << QueryValue{ + key: key + value: value } - a.data << value - v.data[key] = a v.len = v.data.len } // del deletes the values associated with key. pub fn (mut v Values) del(key string) { - v.data.delete(key) + for idx, qvalue in v.data { + if qvalue.key == key { + v.data.delete(idx) + } + } v.len = v.data.len } + +// return the list of values in the query string +pub fn (v Values) values() []string { + mut values := []string{} + for qvalue in v.data { + if qvalue.value != '' { + values << qvalue.value + } + } + return values +} + +// return a map of the query string +pub fn (v Values) to_map() map[string][]string { + mut result := map[string][]string{} + + for qvalue in v.data { + if qvalue.key in result { + result[qvalue.key] << qvalue.value + } else { + result[qvalue.key] = [qvalue.value] + } + } + + return result +} diff --git a/vlib/vweb/parse.v b/vlib/vweb/parse.v index f28d7a1a0d..61452b9f00 100644 --- a/vlib/vweb/parse.v +++ b/vlib/vweb/parse.v @@ -49,8 +49,8 @@ fn parse_attrs(name string, attrs []string) ?([]http.Method, string) { fn parse_query_from_url(url urllib.URL) map[string]string { mut query := map[string]string{} - for k, v in url.query().data { - query[k] = v.data[0] + for qvalue in url.query().data { + query[qvalue.key] = qvalue.value } return query }