diff options
author | bptato <nincsnevem662@gmail.com> | 2024-10-01 21:09:22 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-10-01 21:18:59 +0200 |
commit | b0a511f900a2884c0d1bb55e3991c068ef3e37f2 (patch) | |
tree | 2ac5d77d3f62d844335d0dd00e840a8f5b38d726 /adapter/protocol | |
parent | ee113a5643ed32b7f6ed2522aed92ac700021097 (diff) | |
download | chawan-b0a511f900a2884c0d1bb55e3991c068ef3e37f2.tar.gz |
ftp: remove libcurl dependency
This splits out sftp into a separate binary that *does* depend on libcurl. However, ftp now uses the same socket code as gopher. ftps is dropped, because I've never even tested it. Maybe I'll add it back when we have working OpenSSL bindings. This is still "doing the easy part first", now I have no clue how to handle sftp because my initial plan ("just use the sftp binary") doesn't work - sftp batch mode doesn't accept passwords. libssh2 remains the sole candidate, but that's what libcurl wraps anyway.
Diffstat (limited to 'adapter/protocol')
-rw-r--r-- | adapter/protocol/ftp.nim | 360 | ||||
-rw-r--r-- | adapter/protocol/lcgi.nim | 34 | ||||
-rw-r--r-- | adapter/protocol/sftp.nim | 318 |
3 files changed, 464 insertions, 248 deletions
diff --git a/adapter/protocol/ftp.nim b/adapter/protocol/ftp.nim index f46f8aee..986380d9 100644 --- a/adapter/protocol/ftp.nim +++ b/adapter/protocol/ftp.nim @@ -1,94 +1,16 @@ import std/options import std/os +import std/posix import std/strutils -import curl -import curlerrors -import curlwrap +import lcgi import dirlist import utils/twtstr -type FtpHandle = ref object - curl: CURL - buffer: string - dirmode: bool - base: string - path: string - statusline: bool - -proc printHeader(op: FtpHandle) = - if op.dirmode: - stdout.write("""Content-Type: text/html - -<HTML> -<HEAD> -<BASE HREF=""" & op.base & """> -<TITLE>""" & op.path & """</TITLE> -</HEAD> -<BODY> -<H1>Index of """ & htmlEscape(op.path) & """</H1> -<PRE> -""") - else: - stdout.write('\n') - -proc curlWriteHeader(p: cstring; size, nitems: csize_t; userdata: pointer): - csize_t {.cdecl.} = - var line = newString(nitems) - if nitems > 0: - copyMem(addr line[0], p, nitems) - let op = cast[FtpHandle](userdata) - if not op.statusline: - if line.startsWith("150") or line.startsWith("125"): - op.statusline = true - var status: clong - op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) - stdout.write("Status: " & $status & "\n") - op.printHeader() - return nitems - elif line.startsWith("530"): # login incorrect - op.statusline = true - var status: clong - op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) - # unauthorized (shim http) - stdout.write(""" -Status: 401 -Content-Type: text/html - -<HTML> -<HEAD> -<TITLE>Unauthorized</TITLE> -</HEAD> -<BODY> -<PRE> -""" & htmlEscape(line) & """ -</PRE> -</BODY> -</HTML> -""") - return nitems - -# From the documentation: size is always 1. -proc curlWriteBody(p: cstring; size, nmemb: csize_t; userdata: pointer): - csize_t {.cdecl.} = - let op = cast[FtpHandle](userdata) - if not op.statusline: - op.statusline = true - op.printHeader() - if nmemb > 0: - if op.dirmode: - let i = op.buffer.len - op.buffer.setLen(op.buffer.len + int(nmemb)) - copyMem(addr op.buffer[i], p, nmemb) - else: - return csize_t(stdout.writeBuffer(p, int(nmemb))) - return nmemb - -proc finish(op: FtpHandle) = - let op = op +proc finish(buffer: string) = var items: seq[DirlistItem] = @[] - for line in op.buffer.split('\n'): + for line in buffer.split('\n'): if line.len == 0: continue var i = 10 # permission template skip_till_space = @@ -122,7 +44,10 @@ proc finish(op: FtpHandle) = skip_till_space # y let dates = line.substr(datestarti, i) inc i - let name = line.substr(i) + var j = line.len + if line[^1] == '\r': + dec j + let name = line.substr(i, j - 1) if name == "." or name == "..": continue case line[0] of 'l': # link @@ -151,172 +76,133 @@ proc finish(op: FtpHandle) = stdout.write(makeDirlist(items)) stdout.write("\n</PRE>\n</BODY>\n</HTML>\n") -proc matchesPattern(s, pat: openArray[char]): bool = - var i = 0 - for j, c in pat: - if c == '*': - while i < s.len: - if s.toOpenArray(i, s.high).matchesPattern(pat.toOpenArray(j + 1, - pat.high)): - return true - inc i - return false - if i >= s.len or c != '?' and c != s[i]: - return false - inc i - return true +proc sendCommand(os, ps: PosixStream; cmd, param: string; outs: var string): + int32 = + if cmd != "": + if param == "": + ps.sendDataLoop(cmd & "\r\n") + else: + ps.sendDataLoop(cmd & ' ' & param & "\r\n") + var buf = newString(4) + try: + ps.recvDataLoop(buf) + except EOFError: + os.die("InvalidResponse") + if buf.len < 4: + os.die("InvalidResponse") + outs = "" + while (let c = ps.sreadChar(); c != '\n'): + outs &= c + let status = parseInt32(buf.toOpenArray(0, 2)).get(-1) + if buf[3] == ' ': + return status + buf[3] = ' ' + while true: # multiline + var lbuf = "" + while (let c = ps.sreadChar(); c != '\n'): + lbuf &= c + outs &= lbuf + if lbuf.startsWith(buf): + break + return status -proc matchesPattern(s: string; pats: openArray[string]): bool = - for pat in pats: - if s.matchesPattern(pat): - return true - return false +proc sdie(os: PosixStream; status: int; s, obuf: string) {.noreturn.} = + os.sendDataLoop("Status: " & $status & "\nContent-Type: text/html\n\n" & """ +<h1>""" & s & """</h1> -proc parseSSHConfig(f: File; curl: CURL; host: string; idSet: var bool) = - var skipTillNext = false - var line: string - var certificateFile = "" - var identityFile = "" - while f.readLine(line): - var i = line.skipBlanks(0) - if i == line.len or line[i] == '#': - continue - let k = line.until(AsciiWhitespace, i) - i = line.skipBlanks(i + k.len) - if i < line.len and line[i] == '=': - i = line.skipBlanks(i + 1) - if i == line.len or line[i] == '#': - continue - var args = newSeq[string]() - while i < line.len: - let isStr = line[i] in {'"', '\''} - if isStr: - inc i - var quot = false - var arg = "" - while i < line.len: - if not quot: - if line[i] == '\\': - quot = true - continue - elif line[i] == '"' and isStr or line[i] == ' ' and not isStr: - inc i - break - quot = false - arg &= line[i] - inc i - if arg.len > 0: - args.add(arg) - if k == "Match": #TODO support this - skipTillNext = true - elif k == "Host": - skipTillNext = not host.matchesPattern(args) - elif skipTillNext: - continue - elif k == "IdentityFile": - if args.len != 1: - continue # error - identityFile = expandTilde(args[0]) - elif k == "CertificateFile": - if args.len != 1: - continue # error - certificateFile = expandTilde(args[0]) - if identityFile != "": - curl.setopt(CURLOPT_SSH_PRIVATE_KEYFILE, identityFile) - idSet = true - if certificateFile != "": - curl.setopt(CURLOPT_SSH_PUBLIC_KEYFILE, certificateFile) - f.close() +The server has returned the following message: + +<plaintext> +""" & obuf) + +const Success = 200 .. 299 +proc passiveMode(os, ps: PosixStream; host: string; ipv6: bool): PosixStream = + var obuf = "" + if ipv6: + if os.sendCommand(ps, "EPSV", "", obuf) != 229: + os.die("InvalidResponse") + var i = obuf.find('(') + if i == -1: + os.die("InvalidResponse") + i += 4 # skip delims + let j = obuf.find(')', i) + if j == -1: + os.die("InvalidResponse") + let port = obuf.substr(i, j - 2) + return os.connectSocket(host, port) + if os.sendCommand(ps, "PASV", "", obuf) notin Success: + os.sdie(500, "Couldn't enter passive mode", obuf) + let i = obuf.find(AsciiDigit) + if i == -1: + os.die("InvalidResponse") + var j = obuf.find(AllChars - AsciiDigit - {','}, i) + if j == -1: + j = obuf.len + let ss = obuf.substr(i, j - 1).split(',') + if ss.len < 6: + os.die("InvalidResponse") + var ipv4 = ss[0] + for x in ss.toOpenArray(1, 3): + ipv4 &= '.' + ipv4 &= x + let x = parseUInt16(ss[4]) + let y = parseUInt16(ss[5]) + if x.isNone or y.isNone: + os.die("InvalidResponse") + let port = $((x.get shl 8) or y.get) + return os.connectSocket(host, port) proc main() = - let curl = curl_easy_init() - doAssert curl != nil + let os = newPosixStream(STDOUT_FILENO) var opath = getEnv("MAPPED_URI_PATH") if opath == "": opath = "/" let path = percentDecode(opath) - let op = FtpHandle( - curl: curl, - dirmode: path.len > 0 and path[^1] == '/' - ) - let url = curl_url() - const flags = cuint(CURLU_PATH_AS_IS) - let scheme = getEnv("MAPPED_URI_SCHEME") - url.set(CURLUPART_SCHEME, scheme, flags) - let username = getEnv("MAPPED_URI_USERNAME") - if username != "": - url.set(CURLUPART_USER, username, flags) + let dirmode = path.len > 0 and path[^1] == '/' let host = getEnv("MAPPED_URI_HOST") + let username = getEnv("MAPPED_URI_USERNAME") let password = getEnv("MAPPED_URI_PASSWORD") - var idSet = false - # Parse SSH config for sftp. - if scheme == "sftp": - let systemConfig = "/etc/ssh/ssh_config" - if fileExists(systemConfig): - var f: File - if f.open(systemConfig): - parseSSHConfig(f, curl, host, idSet) - let userConfig = expandTilde("~/.ssh/config") - if fileExists(userConfig): - var f: File - if f.open(userConfig): - parseSSHConfig(f, curl, host, idSet) - if idSet: - curl.setopt(CURLOPT_KEYPASSWD, password) - url.set(CURLUPART_PASSWORD, password, flags) - url.set(CURLUPART_HOST, host, flags) - let port = getEnv("MAPPED_URI_PORT") - if port != "": - url.set(CURLUPART_PORT, port, flags) - # By default, cURL CWD's into relative paths, and an extra slash is - # necessary to specify absolute paths. - # This is incredibly confusing, and probably not what the user wanted. - # So we work around it by adding the extra slash ourselves. - # - # But before that, we take the serialized URL without the path for - # setting the base URL: - url.set(CURLUPART_PATH, opath, flags) - if op.dirmode: - let surl = url.get(CURLUPART_URL, cuint(CURLU_PUNY2IDN)) - if surl == nil: - stdout.write("Cha-Control: ConnectionError InvalidURL\n") - curl_url_cleanup(url) - curl_easy_cleanup(curl) - return - op.base = $surl - op.path = path - curl_free(surl) - # Now for the workaround: - if scheme != "sftp" and (opath.len <= 1 or opath[1] != '~'): - url.set(CURLUPART_PATH, '/' & opath, flags) - # Another hack: if password was set for the identity file, then clear it from - # the URL. - if idSet: - url.set(CURLUPART_PASSWORD, nil, flags) - # Set opts for the request - curl.setopt(CURLOPT_CURLU, url) - curl.setopt(CURLOPT_HEADERDATA, op) - curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader) - curl.setopt(CURLOPT_WRITEDATA, op) - curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody) - curl.setopt(CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD) - let purl = getEnv("ALL_PROXY") - if purl != "": - curl.setopt(CURLOPT_PROXY, purl) + var port = getEnv("MAPPED_URI_PORT") + if port == "": + port = "21" if getEnv("REQUEST_METHOD") != "GET": # fail - stdout.write("Cha-Control: ConnectionError InvalidMethod\n") + os.die("InvalidMethod") + var ipv6: bool + let ps = os.connectSocket(host, port, ipv6) + var obuf = "" + if os.sendCommand(ps, "", "", obuf) != 220: + let s = obuf.deleteChars({'\n', '\r'}) + os.die("ConnectionRefused " & s) + var ustatus = os.sendCommand(ps, "USER", username, obuf) + if ustatus == 331: + ustatus = os.sendCommand(ps, "PASS", password, obuf) + if ustatus in Success: + discard # no need for pass + else: + os.sdie(401, "Unauthorized", obuf) + discard os.sendCommand(ps, "TYPE", "I", obuf) # request raw data + let passive = os.passiveMode(ps, host, ipv6) + if dirmode: + if os.sendCommand(ps, "LIST", "", obuf) == 550: + os.sdie(404, "Not found", obuf) + os.sendDataLoop("""Content-Type: text/html + +<HTML> +<BODY> +<H1>Index of """ & htmlEscape(path) & """</H1> +<PRE>""") + let buffer = passive.recvAll() + finish(buffer) else: - let res = curl_easy_perform(curl) - if res != CURLE_OK: - if not op.statusline: - if res == CURLE_LOGIN_DENIED: - stdout.write("Status: 401\n") - else: - stdout.write(getCurlConnectionError(res)) - elif op.dirmode: - op.finish() - curl_url_cleanup(url) - curl_easy_cleanup(curl) + if os.sendCommand(ps, "RETR", path, obuf) == 550: + os.sdie(404, "Not found", obuf) + os.sendDataLoop("\n") + var buffer {.noinit.}: array[4096, uint8] + while true: + let n = passive.recvData(buffer) + if n == 0: + break + os.sendDataLoop(buffer.toOpenArray(0, n - 1)) main() diff --git a/adapter/protocol/lcgi.nim b/adapter/protocol/lcgi.nim index 03926d4a..e33f0609 100644 --- a/adapter/protocol/lcgi.nim +++ b/adapter/protocol/lcgi.nim @@ -9,12 +9,14 @@ import utils/twtstr export dynstream export twtstr +export STDIN_FILENO, STDOUT_FILENO + proc die*(os: PosixStream; s: string) = os.sendDataLoop("Cha-Control: ConnectionError " & s) quit(1) proc openSocket(os: PosixStream; host, port, resFail, connFail: string; - res: var ptr AddrInfo): SocketHandle = + res: var ptr AddrInfo; outIpv6: var bool): SocketHandle = var err: cint for family in [AF_INET, AF_INET6, AF_UNSPEC]: var hints = AddrInfo( @@ -28,19 +30,20 @@ proc openSocket(os: PosixStream; host, port, resFail, connFail: string; if err < 0: os.die(resFail & ' ' & $gai_strerror(err)) let sock = socket(res.ai_family, res.ai_socktype, res.ai_protocol) - freeaddrinfo(res) if cint(sock) < 0: os.die("InternalError could not open socket") return sock -proc connectSocket(os: PosixStream; host, port, resFail, connFail: string): - PosixStream = +proc connectSocket(os: PosixStream; host, port, resFail, connFail: string; + outIpv6: var bool): PosixStream = var res: ptr AddrInfo - let sock = os.openSocket(host, port, resFail, connFail, res) + let sock = os.openSocket(host, port, resFail, connFail, res, outIpv6) let ps = newPosixStream(sock) if connect(sock, res.ai_addr, res.ai_addrlen) < 0: ps.sclose() os.die(connFail) + outIpv6 = res.ai_family == AF_INET6 + freeaddrinfo(res) return ps proc authenticateSocks5(os, ps: PosixStream; buf: array[2, uint8]; @@ -101,8 +104,9 @@ proc sendSocks5Domain(os, ps: PosixStream; host, port: string) = proc connectSocks5Socket(os: PosixStream; host, port, proxyHost, proxyPort, proxyUser, proxyPass: string): PosixStream = + var dummy = false let ps = os.connectSocket(proxyHost, proxyPort, "FailedToResolveProxy", - "ProxyRefusedToConnect") + "ProxyRefusedToConnect", dummy) const NoAuth = "\x05\x01\x00" const WithAuth = "\x05\x02\x00\x02" ps.sendDataLoop(if proxyUser != "": NoAuth else: WithAuth) @@ -112,8 +116,8 @@ proc connectSocks5Socket(os: PosixStream; host, port, proxyHost, proxyPort, os.sendSocks5Domain(ps, host, port) return ps -proc connectProxySocket(os: PosixStream; host, port, proxy: string): - PosixStream = +proc connectProxySocket(os: PosixStream; host, port, proxy: string; + outIpv6: var bool): PosixStream = let scheme = proxy.until(':') # We always use socks5h, actually. if scheme != "socks5" and scheme != "socks5h": @@ -141,9 +145,17 @@ proc connectProxySocket(os: PosixStream; host, port, proxy: string): let proxyPort = proxy.substr(i) return os.connectSocks5Socket(host, port, proxyHost, proxyPort, user, pass) -proc connectSocket*(os: PosixStream; host, port: string): PosixStream = +# Note: outIpv6 is not read; it just indicates whether the socket's +# address is IPv6. +# In case we connect to a proxy, only the target matters. +proc connectSocket*(os: PosixStream; host, port: string; outIpv6: var bool): + PosixStream = let proxy = getEnv("ALL_PROXY") if proxy != "": - return os.connectProxySocket(host, port, proxy) + return os.connectProxySocket(host, port, proxy, outIpv6) return os.connectSocket(host, port, "FailedToResolveHost", - "ConnectionRefused") + "ConnectionRefused", outIpv6) + +proc connectSocket*(os: PosixStream; host, port: string): PosixStream = + var dummy = false + return os.connectSocket(host, port, dummy) diff --git a/adapter/protocol/sftp.nim b/adapter/protocol/sftp.nim new file mode 100644 index 00000000..a7dae913 --- /dev/null +++ b/adapter/protocol/sftp.nim @@ -0,0 +1,318 @@ +import std/options +import std/os +import std/strutils + +import curl +import curlerrors +import curlwrap +import dirlist + +import utils/twtstr + +type FtpHandle = ref object + curl: CURL + buffer: string + dirmode: bool + base: string + path: string + statusline: bool + +proc printHeader(op: FtpHandle) = + if op.dirmode: + stdout.write("""Content-Type: text/html + +<HTML> +<HEAD> +<BASE HREF=""" & op.base & """> +<TITLE>""" & op.path & """</TITLE> +</HEAD> +<BODY> +<H1>Index of """ & htmlEscape(op.path) & """</H1> +<PRE> +""") + else: + stdout.write('\n') + +proc curlWriteHeader(p: cstring; size, nitems: csize_t; userdata: pointer): + csize_t {.cdecl.} = + var line = newString(nitems) + if nitems > 0: + copyMem(addr line[0], p, nitems) + let op = cast[FtpHandle](userdata) + if not op.statusline: + if line.startsWith("150") or line.startsWith("125"): + op.statusline = true + var status: clong + op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) + stdout.write("Status: " & $status & "\n") + op.printHeader() + return nitems + elif line.startsWith("530"): # login incorrect + op.statusline = true + var status: clong + op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) + # unauthorized (shim http) + stdout.write(""" +Status: 401 +Content-Type: text/html + +<HTML> +<HEAD> +<TITLE>Unauthorized</TITLE> +</HEAD> +<BODY> +<PRE> +""" & htmlEscape(line) & """ +</PRE> +</BODY> +</HTML> +""") + return nitems + +# From the documentation: size is always 1. +proc curlWriteBody(p: cstring; size, nmemb: csize_t; userdata: pointer): + csize_t {.cdecl.} = + let op = cast[FtpHandle](userdata) + if not op.statusline: + op.statusline = true + op.printHeader() + if nmemb > 0: + if op.dirmode: + let i = op.buffer.len + op.buffer.setLen(op.buffer.len + int(nmemb)) + copyMem(addr op.buffer[i], p, nmemb) + else: + return csize_t(stdout.writeBuffer(p, int(nmemb))) + return nmemb + +proc finish(op: FtpHandle) = + let op = op + var items: seq[DirlistItem] = @[] + for line in op.buffer.split('\n'): + if line.len == 0: continue + var i = 10 # permission + template skip_till_space = + while i < line.len and line[i] != ' ': + inc i + # link count + i = line.skipBlanks(i) + while i < line.len and line[i] in AsciiDigit: + inc i + # owner + i = line.skipBlanks(i) + skip_till_space + # group + i = line.skipBlanks(i) + while i < line.len and line[i] != ' ': + inc i + # size + i = line.skipBlanks(i) + var sizes = "" + while i < line.len and line[i] in AsciiDigit: + sizes &= line[i] + inc i + let nsize = parseInt64(sizes).get(-1) + # date + i = line.skipBlanks(i) + let datestarti = i + skip_till_space # m + i = line.skipBlanks(i) + skip_till_space # d + i = line.skipBlanks(i) + skip_till_space # y + let dates = line.substr(datestarti, i) + inc i + let name = line.substr(i) + if name == "." or name == "..": continue + case line[0] + of 'l': # link + let linki = name.find(" -> ") + let linkfrom = name.substr(0, linki - 1) + let linkto = name.substr(linki + 4) # you? + items.add(DirlistItem( + t: ditLink, + name: linkfrom, + modified: dates, + linkto: linkto + )) + of 'd': # directory + items.add(DirlistItem( + t: ditDir, + name: name, + modified: dates + )) + else: # file + items.add(DirlistItem( + t: ditFile, + name: name, + modified: dates, + nsize: int(nsize) + )) + stdout.write(makeDirlist(items)) + stdout.write("\n</PRE>\n</BODY>\n</HTML>\n") + +proc matchesPattern(s, pat: openArray[char]): bool = + var i = 0 + for j, c in pat: + if c == '*': + while i < s.len: + if s.toOpenArray(i, s.high).matchesPattern(pat.toOpenArray(j + 1, + pat.high)): + return true + inc i + return false + if i >= s.len or c != '?' and c != s[i]: + return false + inc i + return true + +proc matchesPattern(s: string; pats: openArray[string]): bool = + for pat in pats: + if s.matchesPattern(pat): + return true + return false + +proc parseSSHConfig(f: File; curl: CURL; host: string; idSet: var bool) = + var skipTillNext = false + var line: string + var certificateFile = "" + var identityFile = "" + while f.readLine(line): + var i = line.skipBlanks(0) + if i == line.len or line[i] == '#': + continue + let k = line.until(AsciiWhitespace, i) + i = line.skipBlanks(i + k.len) + if i < line.len and line[i] == '=': + i = line.skipBlanks(i + 1) + if i == line.len or line[i] == '#': + continue + var args = newSeq[string]() + while i < line.len: + let isStr = line[i] in {'"', '\''} + if isStr: + inc i + var quot = false + var arg = "" + while i < line.len: + if not quot: + if line[i] == '\\': + quot = true + continue + elif line[i] == '"' and isStr or line[i] == ' ' and not isStr: + inc i + break + quot = false + arg &= line[i] + inc i + if arg.len > 0: + args.add(arg) + if k == "Match": #TODO support this + skipTillNext = true + elif k == "Host": + skipTillNext = not host.matchesPattern(args) + elif skipTillNext: + continue + elif k == "IdentityFile": + if args.len != 1: + continue # error + identityFile = expandTilde(args[0]) + elif k == "CertificateFile": + if args.len != 1: + continue # error + certificateFile = expandTilde(args[0]) + if identityFile != "": + curl.setopt(CURLOPT_SSH_PRIVATE_KEYFILE, identityFile) + idSet = true + if certificateFile != "": + curl.setopt(CURLOPT_SSH_PUBLIC_KEYFILE, certificateFile) + f.close() + +proc main() = + let curl = curl_easy_init() + doAssert curl != nil + var opath = getEnv("MAPPED_URI_PATH") + if opath == "": + opath = "/" + let path = percentDecode(opath) + let op = FtpHandle( + curl: curl, + dirmode: path.len > 0 and path[^1] == '/' + ) + let url = curl_url() + const flags = cuint(CURLU_PATH_AS_IS) + let scheme = getEnv("MAPPED_URI_SCHEME") + url.set(CURLUPART_SCHEME, scheme, flags) + let username = getEnv("MAPPED_URI_USERNAME") + if username != "": + url.set(CURLUPART_USER, username, flags) + let host = getEnv("MAPPED_URI_HOST") + let password = getEnv("MAPPED_URI_PASSWORD") + var idSet = false + # Parse SSH config for sftp. + let systemConfig = "/etc/ssh/ssh_config" + if fileExists(systemConfig): + var f: File + if f.open(systemConfig): + parseSSHConfig(f, curl, host, idSet) + let userConfig = expandTilde("~/.ssh/config") + if fileExists(userConfig): + var f: File + if f.open(userConfig): + parseSSHConfig(f, curl, host, idSet) + if idSet: + curl.setopt(CURLOPT_KEYPASSWD, password) + url.set(CURLUPART_PASSWORD, password, flags) + url.set(CURLUPART_HOST, host, flags) + let port = getEnv("MAPPED_URI_PORT") + if port != "": + url.set(CURLUPART_PORT, port, flags) + # By default, cURL CWD's into relative paths, and an extra slash is + # necessary to specify absolute paths. + # This is incredibly confusing, and probably not what the user wanted. + # So we work around it by adding the extra slash ourselves. + # + # But before that, we take the serialized URL without the path for + # setting the base URL: + url.set(CURLUPART_PATH, opath, flags) + if op.dirmode: + let surl = url.get(CURLUPART_URL, cuint(CURLU_PUNY2IDN)) + if surl == nil: + stdout.write("Cha-Control: ConnectionError InvalidURL\n") + curl_url_cleanup(url) + curl_easy_cleanup(curl) + return + op.base = $surl + op.path = path + curl_free(surl) + # Another hack: if password was set for the identity file, then clear it from + # the URL. + if idSet: + url.set(CURLUPART_PASSWORD, nil, flags) + # Set opts for the request + curl.setopt(CURLOPT_CURLU, url) + curl.setopt(CURLOPT_HEADERDATA, op) + curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader) + curl.setopt(CURLOPT_WRITEDATA, op) + curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody) + curl.setopt(CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD) + let purl = getEnv("ALL_PROXY") + if purl != "": + curl.setopt(CURLOPT_PROXY, purl) + if getEnv("REQUEST_METHOD") != "GET": + # fail + stdout.write("Cha-Control: ConnectionError InvalidMethod\n") + else: + let res = curl_easy_perform(curl) + if res != CURLE_OK: + if not op.statusline: + if res == CURLE_LOGIN_DENIED: + stdout.write("Status: 401\n") + else: + stdout.write(getCurlConnectionError(res)) + elif op.dirmode: + op.finish() + curl_url_cleanup(url) + curl_easy_cleanup(curl) + +main() |