12module remote34import net.http5import net.urllib67pub struct Web {8pub mut:9 base string10}1112pub fn new_web(base string) !Web {13 return Web{ base: kepr_host(base)! }14}15161718pub fn kepr_host(value string) !string {19 for letter in value {20 if letter <= ` ` {21 return error('"${value}" is not a Kepr address — try kepr.uk')22 }23 }24 text := if value.starts_with('http://') || value.starts_with('https://') {25 value26 } else {27 'https://${value}'28 }29 parsed := urllib.parse(text) or { return error('"${value}" is not a Kepr address — try kepr.uk') }30 if parsed.scheme !in ['http', 'https'] || parsed.host == '' || parsed.path.trim_right('/') != '' || parsed.raw_query != '' || parsed.fragment != '' {31 return error('"${value}" is not a Kepr address — try kepr.uk')32 }33 if text.all_after('//').contains('@') {34 return error('"${value}" is not a Kepr address — try kepr.uk')35 }36 37 38 if parsed.scheme == 'http' && parsed.hostname().to_lower() !in ['127.0.0.1', 'localhost', '::1'] {39 return error('"${value}" needs https — plain http is only for a Kepr on this machine')40 }41 42 return '${parsed.scheme}://${parsed.host.to_lower()}'43}4445pub fn (mut w Web) get(key string) !Object {46 response := http.fetch(47 url: w.url(key)48 method: .get49 enable_http2: false50 allow_redirect: false51 )!52 if response.status_code == 404 {53 return Object{}54 }55 if response.status_code < 200 || response.status_code >= 300 {56 return error('web GET ${key} failed: ${response.status_code}')57 }58 return Object{59 found: true60 body: response.body.bytes()61 tag: response.header.get_custom('etag', http.HeaderQueryConfig{}) or { '' }62 }63}6465pub fn (mut w Web) list(prefix string) ![]string {66 response := http.fetch(67 url: w.url(prefix)68 method: .get69 enable_http2: false70 allow_redirect: false71 )!72 if response.status_code == 404 {73 return []string{}74 }75 if response.status_code < 200 || response.status_code >= 300 {76 return error('web GET ${prefix} failed: ${response.status_code}')77 }78 mut result := []string{}79 for id in response.body.split_into_lines() {80 if id.trim_space() != '' { result << '${prefix}/${id.trim_space()}' }81 }82 return result83}8485pub fn (mut w Web) put_if_absent(key string, body []u8) !bool {86 return error('${w.repo(key)} is read-only.')87}8889pub fn (mut w Web) put_if_match(key string, body []u8, tag string) !bool {90 return error('${w.repo(key)} is read-only.')91}9293pub fn (mut w Web) delete(key string) ! {94 return error('${w.repo(key)} is read-only.')95}9697fn (w &Web) url(key string) string {98 parts := key.split('/')99 return '${w.base}/${parts[0]}/koh/${parts[1..].join('/')}'100}101102fn (w &Web) repo(key string) string {103 return '${w.base}/${key.all_before('/')}'104}