diff options
-rw-r--r-- | src/config/config.nim | 5 | ||||
-rw-r--r-- | src/config/cookie.nim | 25 | ||||
-rw-r--r-- | src/local/container.nim | 2 | ||||
-rw-r--r-- | src/local/pager.nim | 99 | ||||
-rw-r--r-- | src/server/buffer.nim | 1 | ||||
-rw-r--r-- | src/server/connecterror.nim | 2 | ||||
-rw-r--r-- | src/server/loader.nim | 214 | ||||
-rw-r--r-- | src/server/loaderiface.nim | 3 | ||||
-rw-r--r-- | test/net/cookie.css.http | 4 | ||||
-rw-r--r-- | test/net/cookie.http | 4 | ||||
-rwxr-xr-x | test/net/run.sh | 5 |
11 files changed, 253 insertions, 111 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index f587754c..972ff0f0 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -66,11 +66,6 @@ type frtData = "data" frtMailto = "mailto" - CookieMode* = enum - cmNone = "false" - cmReadOnly = "true" - cmSave = "save" - SiteConfig* = ref object url*: Option[Regex] host*: Option[Regex] diff --git a/src/config/cookie.nim b/src/config/cookie.nim index dacbfa0f..60772b3a 100644 --- a/src/config/cookie.nim +++ b/src/config/cookie.nim @@ -25,12 +25,18 @@ type skip: bool CookieJar* = ref object + name*: string cookies*: seq[Cookie] map: Table[string, Cookie] # {host}{path}\t{name} CookieJarMap* = ref object mtime: int64 - jars*: OrderedTable[string, CookieJar] + jars: OrderedTable[cstring, CookieJar] + + CookieMode* = enum + cmNone = "false" + cmReadOnly = "true" + cmSave = "save" proc newCookieJarMap*(): CookieJarMap = return CookieJarMap() @@ -38,6 +44,14 @@ proc newCookieJarMap*(): CookieJarMap = proc newCookieJar*(): CookieJar = return CookieJar() +proc addNew*(map: CookieJarMap; name: sink string): CookieJar = + let jar = CookieJar(name: name) + map.jars[cstring(jar.name)] = jar + return jar + +proc getOrDefault*(map: CookieJarMap; name: string): CookieJar = + return map.jars.getOrDefault(cstring(name)) + proc parseCookieDate(val: string): Option[int64] = # cookie-date const Delimiters = {'\t', ' '..'/', ';'..'@', '['..'`', '{'..'~'} @@ -320,10 +334,9 @@ proc parse(map: CookieJarMap; iq: openArray[char]; warnings: var seq[string]) = if domain[0] == '.': domain.delete(0..0) cookie.domain = domain - cookieJar = map.jars.getOrDefault(domain) + cookieJar = map.getOrDefault(domain) if cookieJar == nil: - cookieJar = CookieJar() - map.jars[domain] = cookieJar + cookieJar = map.addNew(domain) cookie.hostOnly = not state.nextBool(iq) cookie.path = state.nextField(iq) cookie.secure = state.nextBool(iq) @@ -386,8 +399,8 @@ proc write*(map: CookieJarMap; file: string): bool = buf.setLen(0) if cookie.httpOnly: buf &= "#HttpOnly_" - if cookie.domain != name: - buf &= name & "@" + if cstring(cookie.domain) != name: + buf &= $name & "@" if not cookie.hostOnly: buf &= '.' buf &= cookie.domain & '\t' diff --git a/src/local/container.nim b/src/local/container.nim index 3b25f14f..c4f20523 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -1543,7 +1543,7 @@ proc applyResponse*(container: Container; response: Response; let cookieJar = container.loaderConfig.cookieJar if cookieJar != nil: cookieJar.setCookie(response.headers.getAllNoComma("Set-Cookie"), - response.url, container.config.cookieMode == cmSave) + response.url, container.loaderConfig.cookieMode == cmSave) # set referrer policy, if any let referrerPolicy = response.getReferrerPolicy() if container.config.refererFrom: diff --git a/src/local/pager.nim b/src/local/pager.nim index d7264d86..f8073e58 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -429,6 +429,80 @@ proc evalJSFree(opaque: RootRef; src, filename: string) = let pager = Pager(opaque) JS_FreeValue(pager.jsctx, pager.evalJS(src, filename)) +type CookieStreamOpaque = ref object of RootObj + pager: Pager + buffer: string + +proc onReadCookieStream(response: Response) = + const BufferSize = 4096 + let opaque = CookieStreamOpaque(response.opaque) + let pager = opaque.pager + while true: + let olen = opaque.buffer.len + opaque.buffer.setLen(olen + BufferSize) + let n = response.body.readData(addr opaque.buffer[olen], BufferSize) + if n <= 0: + opaque.buffer.setLen(olen) + break + opaque.buffer.setLen(olen + n) + var lastlf = opaque.buffer.rfind('\n') + var i = 0 + # Syntax: {jarId} RS {url} RS {persist?} RS {header} [ CR {header} ... ] LF + # Persist is ASCII digit 0 if persist, 1 if not. + const RS = '\x1E' # ASCII record separator + while i < lastlf: + let jarId = opaque.buffer.until(RS, i) + i += jarId.len + 1 + let urls = opaque.buffer.until(RS, i) + i += urls.len + 1 + let persists = opaque.buffer.until(RS, i) + i += persists.len + 1 + var headers: seq[string] = @[] + while i - 1 < opaque.buffer.len and opaque.buffer[i - 1] != '\n': + let header = opaque.buffer.until({'\n', '\r'}, i) + headers.add(header) + i += header.len + 1 + let cookieJar = pager.cookieJars.getOrDefault(jarId) + let url = parseURL(urls) + let persist = persists != "0" + if cookieJar == nil or url.isNone or persist and persists != "1": + pager.alert("Error: received wrong set-cookie notification") + continue + cookieJar.setCookie(headers, url.get, persist) + if i > 0: + opaque.buffer.delete(0 ..< i) + +proc onFinishCookieStream(response: Response; success: bool) = + let pager = CookieStreamOpaque(response.opaque).pager + pager.alert("Error: cookie stream broken") + +proc initLoader(pager: Pager) = + let clientConfig = LoaderClientConfig( + defaultHeaders: newHeaders(hgRequest, pager.config.network.defaultHeaders), + proxy: pager.config.network.proxy, + filter: newURLFilter(default = true), + ) + let loader = pager.loader + discard loader.addClient(loader.clientPid, clientConfig, -1, isPager = true) + pager.loader.registerFun = proc(fd: int) = + pager.pollData.register(fd, POLLIN) + pager.loader.unregisterFun = proc(fd: int) = + pager.pollData.unregister(fd) + let request = newRequest(newURL("about:cookie-stream").get) + loader.fetch(request).then(proc(res: JSResult[Response]) = + if res.isNone: + pager.alert("failed to open cookie stream") + return + # ugly hack, so that the cookie stream does not keep headless + # instances running + dec loader.mapFds + let response = res.get + response.opaque = CookieStreamOpaque(pager: pager) + response.onRead = onReadCookieStream + response.onFinish = onFinishCookieStream + response.resume() + ) + proc newPager*(config: Config; forkserver: ForkServer; ctx: JSContext; alerts: seq[string]; loader: FileLoader; loaderPid: int): Pager = let pager = Pager( @@ -448,13 +522,7 @@ proc newPager*(config: Config; forkserver: ForkServer; ctx: JSContext; pager.timeouts = newTimeoutState(pager.jsctx, evalJSFree, pager) JS_SetModuleLoaderFunc(pager.jsrt, normalizeModuleName, loadJSModule, nil) JS_SetInterruptHandler(pager.jsrt, interruptHandler, nil) - let clientConfig = LoaderClientConfig( - defaultHeaders: newHeaders(hgRequest, pager.config.network.defaultHeaders), - proxy: pager.config.network.proxy, - filter: newURLFilter(default = true), - ) - discard pager.loader.addClient(pager.loader.clientPid, - clientConfig, -1, isPager = true) + pager.initLoader() block history: let hist = newHistory(pager.config.external.historySize, getTime().toUnix()) let ps = newPosixStream(pager.config.external.historyFile) @@ -808,10 +876,6 @@ proc run*(pager: Pager; pages: openArray[string]; contentType: string; if istream == nil: pager.config.start.headless = hmDump pager.pollData.register(pager.forkserver.estream.fd, POLLIN) - pager.loader.registerFun = proc(fd: int) = - pager.pollData.register(fd, POLLIN) - pager.loader.unregisterFun = proc(fd: int) = - pager.pollData.unregister(fd) case pager.term.start(istream) of tsrSuccess: discard of tsrDA1Fail: @@ -841,8 +905,6 @@ proc run*(pager: Pager; pages: openArray[string]; contentType: string; if pager.config.start.headless == hmFalse: pager.inputLoop() else: - if pager.config.start.headless == hmTrue: # else just dump - pager.headlessLoop() pager.dumpBuffers() # Note: this function does not work correctly if start < x of last written char @@ -1783,7 +1845,6 @@ proc applySiteconf(pager: Pager; url: URL; charsetOverride: Charset; charsetOverride: charsetOverride, protocol: pager.config.protocol, metaRefresh: pager.config.buffer.metaRefresh, - cookieMode: pager.config.buffer.cookie, markLinks: pager.config.buffer.markLinks ) result.userStyle &= string(pager.config.buffer.userStyle) & '\n' @@ -1797,6 +1858,7 @@ proc applySiteconf(pager: Pager; url: URL; charsetOverride: Charset; allowschemes = @["data", "cache", "stream"], default = true ), + cookieMode: pager.config.buffer.cookie, insecureSslNoVerify: false ) var cookieJarId = url.host @@ -1827,7 +1889,7 @@ proc applySiteconf(pager: Pager; url: URL; charsetOverride: Charset; ourl = tmpUrl return if sc.cookie.isSome: - result.cookieMode = sc.cookie.get + loaderConfig.cookieMode = sc.cookie.get if sc.shareCookieJar.isSome: cookieJarId = sc.shareCookieJar.get if sc.scripting.isSome: @@ -1863,11 +1925,10 @@ proc applySiteconf(pager: Pager; url: URL; charsetOverride: Charset; if result.images: result.imageTypes = pager.config.external.mimeTypes.image result.userAgent = loaderConfig.defaultHeaders.getOrDefault("User-Agent") - if result.cookieMode != cmNone: - var cookieJar = pager.cookieJars.jars.getOrDefault(cookieJarId) + if loaderConfig.cookieMode != cmNone: + var cookieJar = pager.cookieJars.getOrDefault(cookieJarId) if cookieJar == nil: - cookieJar = newCookieJar() - pager.cookieJars.jars[cookieJarId] = cookieJar + cookieJar = pager.cookieJars.addNew(cookieJarId) loaderConfig.cookieJar = cookieJar # Load request in a new buffer. diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 489075cf..c36caecf 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -133,7 +133,6 @@ type markLinks*: bool charsetOverride*: Charset metaRefresh*: MetaRefresh - cookieMode*: CookieMode charsets*: seq[Charset] protocol*: Table[string, ProtocolConfig] imageTypes*: Table[string, string] diff --git a/src/server/connecterror.nim b/src/server/connecterror.nim index 41e4c89e..dff4b12d 100644 --- a/src/server/connecterror.nim +++ b/src/server/connecterror.nim @@ -1,4 +1,5 @@ type ConnectionError* = enum + ceCookieStreamExists = -18 ceCGICachedBodyUnavailable = -17 ceCGIOutputHandleNotFound = -16 ceCGIFailedToOpenCacheOutput = -15 @@ -30,6 +31,7 @@ type ConnectionError* = enum ceProxyInvalidResponse = (11, "ProxyInvalidResponse") const ErrorMessages* = [ + ceCookieStreamExists: "cookie stream already exists", ceCGICachedBodyUnavailable: "request body is not ready in the cache", ceCGIOutputHandleNotFound: "request body output handle not found", ceCGIFailedToOpenCacheOutput: "failed to open cache output", diff --git a/src/server/loader.nim b/src/server/loader.nim index 6f9d881f..dcd6d407 100644 --- a/src/server/loader.nim +++ b/src/server/loader.nim @@ -29,6 +29,7 @@ import std/strutils import std/tables import std/times +import config/config import config/cookie import config/urimethodmap import html/script @@ -71,8 +72,7 @@ type LoaderHandle = ref object of RootObj registered: bool # track registered state stream: PosixStream # input/output stream depending on type - when defined(debug): - url: URL + url: URL # URL nominally retrieved by handle before rewrites InputHandle = ref object of LoaderHandle outputs: seq[OutputHandle] # list of outputs to be streamed into @@ -80,6 +80,7 @@ type cacheRef: CachedItem # if this is a tocache handle, a ref to our cache item parser: HeaderParser # only exists for CGI handles rstate: ResponseState # track response state + credentials: bool # normalized to "include" (true) or "omit" (false) contentLen: uint64 # value of Content-Length; uint64.high if no such header bytesSeen: uint64 # number of bytes read until now startTime: Time # time when download of the body was started @@ -89,7 +90,7 @@ type currentBuffer: LoaderBuffer currentBufferIdx: int buffers: Deque[LoaderBuffer] - ownerPid: int + owner: ClientHandle outputId: int istreamAtEnd: bool suspended: bool @@ -153,6 +154,7 @@ type unregWrite: seq[OutputHandle] unregClient: seq[ClientHandle] downloadList: seq[DownloadItem] + cookieStream: OutputHandle LoaderConfig* = object cgiDir*: seq[string] @@ -192,14 +194,21 @@ proc getOutputId(ctx: var LoaderContext): int = inc ctx.outputNum # Create a new loader handle, with the output stream ostream. -proc newInputHandle(ctx: var LoaderContext; ostream: PosixStream; pid: int; - suspended = true): InputHandle = - let handle = InputHandle(cacheId: -1, contentLen: uint64.high) +proc newInputHandle(ctx: var LoaderContext; ostream: PosixStream; + owner: ClientHandle; url: URL; credentials: bool; suspended = true): + InputHandle = + let handle = InputHandle( + cacheId: -1, + contentLen: uint64.high, + url: url, + credentials: credentials + ) let output = OutputHandle( stream: ostream, parent: handle, outputId: ctx.getOutputId(), - ownerPid: pid, + owner: owner, + url: url, suspended: suspended ) ctx.put(output) @@ -215,6 +224,12 @@ template isEmpty(output: OutputHandle): bool = proc newLoaderBuffer(size = LoaderBufferPageSize): LoaderBuffer = return LoaderBuffer(page: newSeqUninitialized[uint8](size)) +proc newLoaderBuffer(s: openArray[char]): LoaderBuffer = + let buffer = newLoaderBuffer(s.len) + buffer.len = s.len + copyMem(addr buffer.page[0], unsafeAddr s[0], s.len) + buffer + proc bufferCleared(output: OutputHandle) = assert output.currentBuffer != nil output.currentBufferIdx = 0 @@ -224,7 +239,7 @@ proc bufferCleared(output: OutputHandle) = output.currentBuffer = nil proc tee(ctx: var LoaderContext; outputIn: OutputHandle; ostream: PosixStream; - pid: int): OutputHandle = + owner: ClientHandle): OutputHandle = assert outputIn.suspended let output = OutputHandle( parent: outputIn.parent, @@ -234,12 +249,11 @@ proc tee(ctx: var LoaderContext; outputIn: OutputHandle; ostream: PosixStream; buffers: outputIn.buffers, istreamAtEnd: outputIn.istreamAtEnd, outputId: ctx.getOutputId(), - ownerPid: pid, - suspended: outputIn.suspended + owner: owner, + suspended: outputIn.suspended, + url: outputIn.url ) ctx.put(output) - when defined(debug): - output.url = outputIn.url if outputIn.parent != nil: assert outputIn.parent.parser == nil outputIn.parent.outputs.add(output) @@ -270,6 +284,23 @@ proc sendResult(ctx: var LoaderContext; handle: InputHandle; res: int; w.swrite(msg) return ctx.pushBuffer(output, buffer, ignoreSuspension = true) +proc updateCookies(ctx: var LoaderContext; cookieJar: CookieJar; + url: URL; owner: ClientHandle; values: openArray[string]) = + # Syntax: {jarId} RS {url} RS {persist?} RS {header} [ CR {header} ... ] LF + # Persist is ASCII digit 0 if persist, 1 if not. + const RS = '\x1E' # ASCII record separator + let persist = if owner.config.cookieMode == cmSave: '1' else: '0' + var s = cookieJar.name & RS & $url & RS & persist & RS + for i, it in values.mypairs: + s &= it & [false: '\r', true: '\n'][i == values.high] + let buffer = newLoaderBuffer(s) + case ctx.pushBuffer(ctx.cookieStream, buffer, ignoreSuspension = false) + of pbrDone: discard + of pbrUnregister: + ctx.unregWrite.add(ctx.cookieStream) + ctx.cookieStream.dead = true + ctx.cookieStream = nil + proc sendStatus(ctx: var LoaderContext; handle: InputHandle; status: uint16; headers: Headers): PushBufferResult = assert handle.rstate == rsBeforeStatus @@ -277,10 +308,19 @@ proc sendStatus(ctx: var LoaderContext; handle: InputHandle; status: uint16; let contentLens = headers.getOrDefault("Content-Length") handle.startTime = getTime() handle.contentLen = parseUInt64(contentLens).get(uint64.high) + let output = handle.output + let cookieJar = output.owner.config.cookieJar + if cookieJar != nil and handle.credentials: + # Never persist in loader; we save cookies in the pager. + let values = headers.getAllNoComma("Set-Cookie") + if values.len > 0: + cookieJar.setCookie(values, handle.url, persist = false) + if ctx.cookieStream != nil: + ctx.updateCookies(cookieJar, handle.url, output.owner, values) let buffer = bufferFromWriter w: w.swrite(status) w.swrite(headers) - return ctx.pushBuffer(handle.output, buffer, ignoreSuspension = true) + return ctx.pushBuffer(output, buffer, ignoreSuspension = true) proc writeData(ps: PosixStream; buffer: LoaderBuffer; si = 0): int {.inline.} = assert buffer.len - si > 0 @@ -350,7 +390,7 @@ func findOutput(ctx: var LoaderContext; id: int; for it in ctx.outputHandles: if it.outputId == id: # verify that it's safe to access this handle. - doAssert ctx.isPrivileged(client) or client.pid == it.ownerPid + doAssert ctx.isPrivileged(client) or client == it.owner return it return nil @@ -464,11 +504,10 @@ proc redirectToFile(ctx: var LoaderContext; output: OutputHandle; stream: ps, istreamAtEnd: output.istreamAtEnd, outputId: ctx.getOutputId(), - bytesSent: osent + bytesSent: osent, + url: output.url ) output.parent.outputs.add(fileOutput) - when defined(debug): - fileOutput.url = output.url return true proc getTempFile(ctx: var LoaderContext): string = @@ -777,17 +816,16 @@ proc findItem(authMap: seq[AuthItem]; origin: Origin): AuthItem = return it return nil -proc includeCredentials(config: LoaderClientConfig; request: Request; url: URL; - header: string): bool = - if header in request.headers: - return false +proc includeCredentials(config: LoaderClientConfig; request: Request; url: URL): + bool = return request.credentialsMode == cmInclude or request.credentialsMode == cmSameOrigin and config.originURL == nil or url.origin.isSameOrigin(config.originURL.origin) proc findAuth(client: ClientHandle; request: Request; url: URL): AuthItem = - if client.config.includeCredentials(request, url, "Authorization"): + if "Authorization" notin request.headers and + client.config.includeCredentials(request, url): if client.authMap.len > 0: return client.authMap.findItem(url.authOrigin) return nil @@ -996,13 +1034,14 @@ proc loadCGI(ctx: var LoaderContext; client: ClientHandle; handle: InputHandle; ostream.sclose() of rbtOutput: ostream.setBlocking(false) - let output = ctx.tee(outputIn, ostream, client.pid) + let output = ctx.tee(outputIn, ostream, client) output.suspended = false if not output.isEmpty: ctx.register(output) of rbtCache: if ostream != nil: - let handle = ctx.newInputHandle(ostream, client.pid, suspended = false) + let handle = ctx.newInputHandle(ostream, client, + newURL("cache:/dev/null").get, credentials = false, suspended = false) handle.stream = istream2 ostream.setBlocking(false) ctx.loadStreamRegular(handle, cachedHandle) @@ -1093,9 +1132,7 @@ proc loadDataSend(ctx: var LoaderContext; handle: InputHandle; s, ct: string) = else: ctx.oclose(output) return - let buffer = newLoaderBuffer(s.len) - buffer.len = s.len - copyMem(addr buffer.page[0], unsafeAddr s[0], s.len) + let buffer = newLoaderBuffer(s) case ctx.pushBuffer(output, buffer, ignoreSuspension = false) of pbrUnregister: if output.registered: @@ -1212,26 +1249,19 @@ proc parseDownloadActions(ctx: LoaderContext; s: string): seq[DownloadAction] = result.sort(proc(a, b: DownloadAction): int = return cmp(a.n, b.n), Descending) -proc loadAbout(ctx: var LoaderContext; handle: InputHandle; request: Request) = - let url = request.url - case url.pathname - of "blank": - ctx.loadDataSend(handle, "", "text/html") - of "chawan": - const body = staticRead"res/chawan.html" - ctx.loadDataSend(handle, body, "text/html") - of "downloads": - if request.httpMethod == hmPost: - # OK/STOP/PAUSE/RESUME clicked - if request.body.t != rbtString: - ctx.rejectHandle(handle, ceInvalidURL, "wat") - return - for it in ctx.parseDownloadActions(request.body.s): - let dl = ctx.downloadList[it.n] - if dl.output != nil: - ctx.unregWrite.add(dl.output) - ctx.downloadList.del(it.n) - var body = """ +proc loadDownloads(ctx: var LoaderContext; handle: InputHandle; + request: Request) = + if request.httpMethod == hmPost: + # OK clicked + if request.body.t != rbtString: + ctx.rejectHandle(handle, ceInvalidURL, "wat") + return + for it in ctx.parseDownloadActions(request.body.s): + let dl = ctx.downloadList[it.n] + if dl.output != nil: + ctx.unregWrite.add(dl.output) + ctx.downloadList.del(it.n) + var body = """ <!DOCTYPE html> <title>Download List Panel</title> <body> @@ -1241,34 +1271,65 @@ proc loadAbout(ctx: var LoaderContext; handle: InputHandle; request: Request) = <hr> <pre> """ - let now = getTime() - var refresh = false - for i, it in ctx.downloadList.mpairs: - if it.output != nil: - it.sent = it.output.bytesSent - if it.output.stream == nil: - it.output = nil - refresh = true - body &= it.makeProgress(now) - body &= "<input type=submit name=stop" & $i - if it.output != nil: - body &= " value=STOP" - else: - body &= " value=OK" - body &= ">" - body &= "<hr>" - if refresh: - body &= "<meta http-equiv=refresh content=1>" # :P - body &= """ + let now = getTime() + var refresh = false + for i, it in ctx.downloadList.mpairs: + if it.output != nil: + it.sent = it.output.bytesSent + if it.output.stream == nil: + it.output = nil + refresh = true + body &= it.makeProgress(now) + body &= "<input type=submit name=stop" & $i + if it.output != nil: + body &= " value=STOP" + else: + body &= " value=OK" + body &= ">" + body &= "<hr>" + if refresh: + body &= "<meta http-equiv=refresh content=1>" # :P + body &= """ </pre> </body> """ + ctx.loadDataSend(handle, body, "text/html") + +# Stream for notifying the pager of new cookies set in the loader. +proc loadCookieStream(ctx: var LoaderContext; handle: InputHandle; + request: Request) = + if ctx.cookieStream != nil: + ctx.rejectHandle(handle, ceCookieStreamExists) + return + case ctx.sendResult(handle, 0) + of pbrDone: discard + of pbrUnregister: + ctx.close(handle) + return + case ctx.sendStatus(handle, 200, newHeaders(hgResponse)) + of pbrDone: discard + of pbrUnregister: + ctx.close(handle) + return + ctx.cookieStream = handle.output + +proc loadAbout(ctx: var LoaderContext; handle: InputHandle; request: Request) = + let url = request.url + case url.pathname + of "blank": + ctx.loadDataSend(handle, "", "text/html") + of "chawan": + const body = staticRead"res/chawan.html" ctx.loadDataSend(handle, body, "text/html") + of "downloads": + ctx.loadDownloads(handle, request) + of "cookie-stream": + ctx.loadCookieStream(handle, request) of "license": const body = staticRead"res/license.md" ctx.loadDataSend(handle, body, "text/markdown") else: - ctx.rejectHandle(handle, ceInvalidURL, "invalid download URL") + ctx.rejectHandle(handle, ceInvalidURL, "invalid about URL") proc loadResource(ctx: var LoaderContext; client: ClientHandle; config: LoaderClientConfig; request: Request; handle: InputHandle) = @@ -1319,12 +1380,13 @@ proc loadResource(ctx: var LoaderContext; client: ClientHandle; if tries >= MaxRewrites: ctx.rejectHandle(handle, ceTooManyRewrites) -proc setupRequestDefaults(request: Request; config: LoaderClientConfig) = +proc setupRequestDefaults(request: Request; config: LoaderClientConfig; + credentials: bool) = for k, v in config.defaultHeaders.allPairs: if k notin request.headers: request.headers[k] = v if config.cookieJar != nil and config.cookieJar.cookies.len > 0: - if config.includeCredentials(request, request.url, "Cookie"): + if "Cookie" notin request.headers and credentials: let cookie = config.cookieJar.serialize(request.url) if cookie != "": request.headers["Cookie"] = cookie @@ -1348,14 +1410,12 @@ proc load(ctx: var LoaderContext; stream: SocketStream; request: Request; discard close(sv[1]) let stream = newSocketStream(sv[0]) stream.setBlocking(false) - let handle = ctx.newInputHandle(stream, client.pid) - when defined(debug): - handle.url = request.url - handle.output.url = request.url + let credentials = config.includeCredentials(request, request.url) + let handle = ctx.newInputHandle(stream, client, request.url, credentials) if not config.filter.match(request.url): ctx.rejectHandle(handle, ceDisallowedURL) else: - request.setupRequestDefaults(config) + request.setupRequestDefaults(config, credentials) ctx.loadResource(client, config, request, handle) proc load(ctx: var LoaderContext; stream: SocketStream; client: ClientHandle; @@ -1555,11 +1615,13 @@ proc tee(ctx: var LoaderContext; stream: SocketStream; client: ClientHandle; r.sread(sourceId) r.sread(targetPid) let outputIn = ctx.findOutput(sourceId, client) + let target = ctx.clientMap.getOrDefault(targetPid) var sv {.noinit.}: array[2, cint] - if outputIn != nil and socketpair(AF_UNIX, SOCK_STREAM, IPPROTO_IP, sv) == 0: + if target != nil and outputIn != nil and + socketpair(AF_UNIX, SOCK_STREAM, IPPROTO_IP, sv) == 0: let ostream = newSocketStream(sv[0]) ostream.setBlocking(false) - let output = ctx.tee(outputIn, ostream, targetPid) + let output = ctx.tee(outputIn, ostream, target) stream.withPacketWriter w: w.swrite(output.outputId) w.sendFd(sv[1]) diff --git a/src/server/loaderiface.nim b/src/server/loaderiface.nim index 4348acce..0b00a22a 100644 --- a/src/server/loaderiface.nim +++ b/src/server/loaderiface.nim @@ -24,7 +24,7 @@ type FileLoader* = ref object clientPid*: int map: seq[MapData] - mapFds: int # number of fds in map + mapFds*: int # number of fds in map unregistered*: seq[int] registerFun*: proc(fd: int) unregisterFun*: proc(fd: int) @@ -83,6 +83,7 @@ type proxy*: URL referrerPolicy*: ReferrerPolicy insecureSslNoVerify*: bool + cookieMode*: CookieMode proc getRedirect*(response: Response; request: Request): Request = if response.status in 301u16..303u16 or response.status in 307u16..308u16: diff --git a/test/net/cookie.css.http b/test/net/cookie.css.http new file mode 100644 index 00000000..72b9a993 --- /dev/null +++ b/test/net/cookie.css.http @@ -0,0 +1,4 @@ +Content-Type: text/css +Set-Cookie: test9=css + +#y { display: none } diff --git a/test/net/cookie.http b/test/net/cookie.http index bd9fabb4..d0abe683 100644 --- a/test/net/cookie.http +++ b/test/net/cookie.http @@ -9,7 +9,9 @@ Set-Cookie: test6=hi; Max-Age=9223372036854775807 Set-Cookie: test7=hi; Expires=Mon 0 Jan 1999 20:30:00 GMT Set-Cookie: test8=hi; Expires=Mon, 31 Feb 1999 20:30:00 GMT +<link rel=stylesheet href=cookie.css.http> <div id=x>Fail</div> +<div id=y>CSS fail</div> <script src=asserts.js></script> <script> const x = new XMLHttpRequest(); @@ -17,6 +19,6 @@ x.open("GET", "headers", false); x.overrideMimeType("text/plain"); x.send(); const cookie = x.responseText.split('\n').find(x => x.startsWith("cookie:")); -assertEquals(cookie.split(': ').pop(), "test=asdfasdf; SID=31d4d96e407aad42; test3=x; test4=y; test6=hi; test7=hi; test8=hi"); +assertEquals(cookie.split(': ').pop(), "test=asdfasdf; SID=31d4d96e407aad42; test3=x; test4=y; test6=hi; test7=hi; test8=hi; test9=css"); document.getElementById("x").textContent = "Success"; </script> diff --git a/test/net/run.sh b/test/net/run.sh index a257cefc..539de55d 100755 --- a/test/net/run.sh +++ b/test/net/run.sh @@ -6,7 +6,10 @@ fi failed=0 for h in *.html *.http -do printf '%s\r' "$h" +do case $h in + cookie.css.http) continue;; + esac + printf '%s\r' "$h" if ! "$CHA" -C config.toml "http://localhost:$1/$h" | diff all.expected - then failed=$(($failed+1)) printf 'FAIL: %s\n' "$h" |