12module remote34import net.http5import net.urllib67pub struct Web {8pub mut:9 base string10 11 on_body fn (received u64) = unsafe { nil }12}1314pub fn new_web(base string) !Web {15 return Web{ base: kepr_host(base)! }16}17181920pub fn kepr_host(value string) !string {21 for letter in value {22 if letter <= ` ` {23 return error('"${value}" is not a Kepr address — try kepr.uk')24 }25 }26 text := if value.starts_with('http://') || value.starts_with('https://') {27 value28 } else {29 'https://${value}'30 }31 parsed := urllib.parse(text) or { return error('"${value}" is not a Kepr address — try kepr.uk') }32 if parsed.scheme !in ['http', 'https'] || parsed.host == '' || parsed.path.trim_right('/') != '' || parsed.raw_query != '' || parsed.fragment != '' {33 return error('"${value}" is not a Kepr address — try kepr.uk')34 }35 if text.all_after('//').contains('@') {36 return error('"${value}" is not a Kepr address — try kepr.uk')37 }38 39 40 if parsed.scheme == 'http' && parsed.hostname().to_lower() !in ['127.0.0.1', 'localhost', '::1'] {41 return error('"${value}" needs https — plain http is only for a Kepr on this machine')42 }43 44 return '${parsed.scheme}://${parsed.host.to_lower()}'45}4647pub fn (mut w Web) get(key string) !Object {48 on_body := w.on_body49 response := http.fetch(50 url: w.url(key)51 method: .get52 enable_http2: false53 allow_redirect: false54 on_progress_body: fn [on_body] (_ &http.Request, chunk []u8, received u64, _ u64, _ int) ! {55 if on_body != unsafe { nil } && (received - u64(chunk.len)) >> 20 != received >> 20 {56 on_body(received)57 }58 }59 )!60 if response.status_code == 404 {61 return Object{}62 }63 if response.status_code < 200 || response.status_code >= 300 {64 return error('web GET ${key} failed: ${response.status_code}')65 }66 return Object{67 found: true68 body: response.body.bytes()69 tag: response.header.get_custom('etag', http.HeaderQueryConfig{}) or { '' }70 }71}7273pub fn (mut w Web) list(prefix string) ![]string {74 response := http.fetch(75 url: w.url(prefix)76 method: .get77 enable_http2: false78 allow_redirect: false79 )!80 if response.status_code == 404 {81 return []string{}82 }83 if response.status_code < 200 || response.status_code >= 300 {84 return error('web GET ${prefix} failed: ${response.status_code}')85 }86 mut result := []string{}87 for id in response.body.split_into_lines() {88 if id.trim_space() != '' { result << '${prefix}/${id.trim_space()}' }89 }90 return result91}9293pub fn (mut w Web) put_if_absent(key string, body []u8) !bool {94 return error('${w.repo(key)} is read-only.')95}9697pub fn (mut w Web) put_if_match(key string, body []u8, tag string) !bool {98 return error('${w.repo(key)} is read-only.')99}100101pub fn (mut w Web) delete(key string) ! {102 return error('${w.repo(key)} is read-only.')103}104105fn (w &Web) url(key string) string {106 parts := key.split('/')107 return '${w.base}/${parts[0]}/koh/${parts[1..].join('/')}'108}109110fn (w &Web) repo(key string) string {111 return '${w.base}/${key.all_before('/')}'112}