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}1516pub fn kepr_host(value string) !string {17 for letter in value {18 if letter <= ` ` {19 return error('"${value}" is not a Kepr address — try kepr.uk')20 }21 }22 text := if value.starts_with('http://') || value.starts_with('https://') {23 value24 } else {25 'https://${value}'26 }27 parsed := urllib.parse(text) or { return error('"${value}" is not a Kepr address — try kepr.uk') }28 if parsed.scheme !in ['http', 'https'] || parsed.host == '' || parsed.path.trim_right('/') != '' || parsed.raw_query != '' || parsed.fragment != '' {29 return error('"${value}" is not a Kepr address — try kepr.uk')30 }31 if text.all_after('//').contains('@') {32 return error('"${value}" is not a Kepr address — try kepr.uk')33 }34 35 36 if parsed.scheme == 'http' && parsed.hostname().to_lower() !in ['127.0.0.1', 'localhost', '::1'] {37 return error('"${value}" needs https — plain http is only for a Kepr on this machine')38 }39 40 return '${parsed.scheme}://${parsed.host.to_lower()}'41}4243pub fn (mut w Web) get(key string) !Object {44 response := http.fetch(45 url: w.url(key)46 method: .get47 enable_http2: false48 allow_redirect: false49 )!50 if response.status_code == 404 {51 return Object{}52 }53 if response.status_code < 200 || response.status_code >= 300 {54 return error('web GET ${key} failed: ${response.status_code}')55 }56 return Object{ found: true, body: response.body.bytes(), tag: response.header.get_custom('etag', http.HeaderQueryConfig{}) or { '' } }57}5859pub fn (mut w Web) list(prefix string) ![]string {60 response := http.fetch(61 url: w.url(prefix)62 method: .get63 enable_http2: false64 allow_redirect: false65 )!66 if response.status_code == 404 {67 return []string{}68 }69 if response.status_code < 200 || response.status_code >= 300 {70 return error('web GET ${prefix} failed: ${response.status_code}')71 }72 mut result := []string{}73 for id in response.body.split_into_lines() {74 if id.trim_space() != '' { result << '${prefix}/${id.trim_space()}' }75 }76 return result77}7879pub fn (mut w Web) put_if_absent(key string, body []u8) !bool {80 return error('${w.repo(key)} is read-only.')81}8283pub fn (mut w Web) put_if_match(key string, body []u8, tag string) !bool {84 return error('${w.repo(key)} is read-only.')85}8687pub fn (mut w Web) delete(key string) ! {88 return error('${w.repo(key)} is read-only.')89}9091fn (w &Web) url(key string) string {92 parts := key.split('/')93 return '${w.base}/${parts[0]}/koh/${parts[1..].join('/')}'94}9596fn (w &Web) repo(key string) string {97 return '${w.base}/${key.all_before('/')}'98}