diff options
-rw-r--r-- | src/html/dom.nim | 20 | ||||
-rw-r--r-- | src/html/xmlhttprequest.nim | 9 | ||||
-rw-r--r-- | src/local/container.nim | 6 | ||||
-rw-r--r-- | src/local/pager.nim | 10 | ||||
-rw-r--r-- | src/server/buffer.nim | 2 | ||||
-rw-r--r-- | src/server/headers.nim | 66 | ||||
-rw-r--r-- | src/server/loader.nim | 18 | ||||
-rw-r--r-- | src/server/loaderiface.nim | 22 | ||||
-rw-r--r-- | src/server/request.nim | 12 | ||||
-rw-r--r-- | src/server/response.nim | 23 |
10 files changed, 101 insertions, 87 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim index db192977..1eeac6c0 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -627,7 +627,7 @@ proc create2DContext(jctx: JSContext; target: HTMLCanvasElement; let request = newRequest( newURL("img-codec+x-cha-canvas:decode").get, httpMethod = hmPost, - headers = newHeaders({"Cha-Image-Info-Only": "1"}), + headers = newHeaders(hgRequest, {"Cha-Image-Info-Only": "1"}), body = RequestBody(t: rbtOutput, outputId: ctlres.outputId) ) let response = loader.doRequest(request) @@ -4263,7 +4263,7 @@ proc loadResource*(window: Window; image: HTMLImageElement) = return let cachedURL = CachedURLImage(expiry: -1, loading: true) window.imageURLCache[surl] = cachedURL - let headers = newHeaders({"Accept": "*/*"}) + let headers = newHeaders(hgRequest, {"Accept": "*/*"}) let p = window.corsFetch(newRequest(url, headers = headers)).then( proc(res: JSResult[Response]): EmptyPromise = if res.isNone: @@ -4293,7 +4293,7 @@ proc loadResource*(window: Window; image: HTMLImageElement) = let request = newRequest( url.get, httpMethod = hmPost, - headers = newHeaders({"Cha-Image-Info-Only": "1"}), + headers = newHeaders(hgRequest, {"Cha-Image-Info-Only": "1"}), body = RequestBody(t: rbtOutput, outputId: response.outputId), ) let r = window.corsFetch(request) @@ -4301,14 +4301,14 @@ proc loadResource*(window: Window; image: HTMLImageElement) = response.close() var expiry = -1i64 if "Cache-Control" in response.headers: - for hdr in response.headers.table["Cache-Control"]: - var i = hdr.find("max-age=") - if i != -1: - i = hdr.skipBlanks(i + "max-age=".len) + for hdr in response.headers.getAllCommaSplit("Cache-Control"): + let s = hdr.strip() + if s.startsWithIgnoreCase("max-age="): + let i = hdr.skipBlanks("max-age=".len) let s = hdr.until(AllChars - AsciiDigit, i) let pi = parseInt64(s) if pi.isSome: - expiry = getTime().utc().toTime().toUnix() + pi.get + expiry = getTime().toUnix() + pi.get break cachedURL.loading = false cachedURL.expiry = expiry @@ -4378,7 +4378,7 @@ proc loadResource*(window: Window; svg: SVGSVGElement) = let request = newRequest( newURL("img-codec+svg+xml:decode").get, httpMethod = hmPost, - headers = newHeaders({"Cha-Image-Info-Only": "1"}), + headers = newHeaders(hgRequest, {"Cha-Image-Info-Only": "1"}), body = RequestBody(t: rbtOutput, outputId: svgres.outputId) ) let p = loader.fetch(request).then(proc(res: JSResult[Response]) = @@ -6016,7 +6016,7 @@ proc toBlob(ctx: JSContext; this: HTMLCanvasElement; callback: JSValueConst; if url0.isNone: return let url = url0.get - let headers = newHeaders({ + let headers = newHeaders(hgRequest, { "Cha-Image-Dimensions": $this.bitmap.width & 'x' & $this.bitmap.height }) if (var quality = quality.get(-1); 0 <= quality and quality <= 1): diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim index 556d32b1..c24fa262 100644 --- a/src/html/xmlhttprequest.nim +++ b/src/html/xmlhttprequest.nim @@ -1,6 +1,5 @@ import std/options import std/strutils -import std/tables import chagashi/charset import chagashi/decoder @@ -83,7 +82,7 @@ func newXMLHttpRequest(): XMLHttpRequest {.jsctor.} = let upload = XMLHttpRequestUpload() return XMLHttpRequest( upload: upload, - headers: newHeaders(), + headers: newHeaders(hgRequest), responseObject: JS_UNDEFINED ) @@ -149,7 +148,7 @@ proc open(ctx: JSContext; this: XMLHttpRequest; httpMethod, url: string; else: this.flags.incl(xhrfSync) this.requestMethod = httpMethod - this.headers = newHeaders() + this.headers = newHeaders(hgRequest) this.response = makeNetworkError() this.received = "" this.requestURL = parsedURL @@ -178,7 +177,7 @@ proc setRequestHeader(this: XMLHttpRequest; name, value: string): return errDOMException("Invalid header name or value", "SyntaxError") if isForbiddenRequestHeader(name, value): return ok() - this.headers.table[name.toHeaderCase()] = @[value] + this.headers[name] = value ok() proc `withCredentials=`(this: XMLHttpRequest; withCredentials: bool): @@ -383,7 +382,7 @@ proc getResponseHeader(ctx: JSContext; this: XMLHttpRequest; name: string): proc getAllResponseHeaders(this: XMLHttpRequest): string {.jsfunc.} = result = "" #TODO sort, should use the filtered header list, etc. - for k, v in this.response.headers.table: + for k, v in this.response.headers: if k.isForbiddenResponseHeaderName(): continue for it in v: diff --git a/src/local/container.nim b/src/local/container.nim index 3c1423f1..3b25f14f 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -1541,9 +1541,9 @@ proc applyResponse*(container: Container; response: Response; mimeTypes: MimeTypes) = # accept cookies let cookieJar = container.loaderConfig.cookieJar - if cookieJar != nil and "Set-Cookie" in response.headers.table: - cookieJar.setCookie(response.headers.table["Set-Cookie"], response.url, - container.config.cookieMode == cmSave) + if cookieJar != nil: + cookieJar.setCookie(response.headers.getAllNoComma("Set-Cookie"), + response.url, container.config.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 3fe65ee5..ac78f8bb 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -450,7 +450,7 @@ proc newPager*(config: Config; forkserver: ForkServer; ctx: JSContext; JS_SetModuleLoaderFunc(pager.jsrt, normalizeModuleName, loadJSModule, nil) JS_SetInterruptHandler(pager.jsrt, interruptHandler, nil) let clientConfig = LoaderClientConfig( - defaultHeaders: newHeaders(pager.config.network.defaultHeaders), + defaultHeaders: newHeaders(hgRequest, pager.config.network.defaultHeaders), proxy: pager.config.network.proxy, filter: newURLFilter(default = true), ) @@ -1052,7 +1052,7 @@ proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap; return newResolvedPromise(res) # resize # use a temp file, so that img-resize can mmap its output - let headers = newHeaders({ + let headers = newHeaders(hgRequest, { "Cha-Image-Dimensions": $bmp.width & 'x' & $bmp.height, "Cha-Image-Target-Dimensions": $image.width & 'x' & $image.height }) @@ -1078,7 +1078,7 @@ proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap; if cachedImage.state == cisCanceled: pager.loader.removeCachedItem(cacheId) return - let headers = newHeaders({ + let headers = newHeaders(hgRequest, { "Cha-Image-Dimensions": $image.width & 'x' & $image.height }) var url: URL = nil @@ -1790,7 +1790,7 @@ proc applySiteconf(pager: Pager; url: URL; charsetOverride: Charset; ) loaderConfig = LoaderClientConfig( originURL: url, - defaultHeaders: newHeaders(pager.config.network.defaultHeaders), + defaultHeaders: newHeaders(hgRequest, pager.config.network.defaultHeaders), cookiejar: nil, proxy: pager.config.network.proxy, filter: newURLFilter( @@ -1847,7 +1847,7 @@ proc applySiteconf(pager: Pager; url: URL; charsetOverride: Charset; if sc.proxy.isSome: loaderConfig.proxy = sc.proxy.get if sc.defaultHeaders != nil: - loaderConfig.defaultHeaders = newHeaders(sc.defaultHeaders[]) + loaderConfig.defaultHeaders = newHeaders(hgRequest, sc.defaultHeaders[]) if sc.insecureSslNoVerify.isSome: loaderConfig.insecureSslNoVerify = sc.insecureSslNoVerify.get if sc.autofocus.isSome: diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 2e8b95b4..403462c0 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -1274,7 +1274,7 @@ proc makeFormRequest(buffer: Buffer; parsedAction: URL; httpMethod: HttpMethod; #TODO with charset let kvlist = entryList.toNameValuePairs() RequestBody(t: rbtString, s: serializePlainTextFormData(kvlist)) - let headers = newHeaders({"Content-Type": $enctype}) + let headers = newHeaders(hgRequest, {"Content-Type": $enctype}) return newRequest(parsedAction, httpMethod, headers, body) # https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm diff --git a/src/server/headers.nim b/src/server/headers.nim index 2ed49e04..25102486 100644 --- a/src/server/headers.nim +++ b/src/server/headers.nim @@ -18,7 +18,7 @@ type hgResponse = "response" Headers* = ref object - table*: Table[string, seq[string]] + table: Table[string, seq[string]] guard*: HeaderGuard HeadersInitType = enum @@ -33,6 +33,20 @@ type jsDestructor(Headers) +func isForbiddenResponseHeaderName*(name: string): bool + +iterator pairs*(this: Headers): (string, string) = + for k, vs in this.table: + if this.guard == hgResponse and k.isForbiddenResponseHeaderName(): + continue + for v in vs: + yield (k, v) + +iterator allPairs*(headers: Headers): (string, string) = + for k, vs in headers.table: + for v in vs: + yield (k, v) + const HTTPWhitespace = {'\n', '\r', '\t', ' '} proc fromJS(ctx: JSContext; val: JSValueConst; res: var HeadersInit): @@ -219,17 +233,12 @@ proc fill*(headers: Headers; init: HeadersInit): JSResult[void] = of hitSequence: return headers.fill(init.s) of hitTable: return headers.fill(init.tab) -func newHeaders*(guard = hgNone): Headers = +func newHeaders*(guard: HeaderGuard): Headers = return Headers(guard: guard) -func newHeaders(obj = none(HeadersInit)): JSResult[Headers] {.jsctor.} = - let headers = Headers(guard: hgNone) - if obj.isSome: - ?headers.fill(obj.get) - return ok(headers) - -func newHeaders*(table: openArray[(string, string)]): Headers = - let headers = Headers() +func newHeaders*(guard: HeaderGuard; table: openArray[(string, string)]): + Headers = + let headers = newHeaders(guard) for (k, v) in table: let k = k.toHeaderCase() headers.table.withValue(k, vs): @@ -238,8 +247,8 @@ func newHeaders*(table: openArray[(string, string)]): Headers = headers.table[k] = @[v] return headers -func newHeaders*(table: Table[string, string]): Headers = - let headers = Headers() +func newHeaders*(guard: HeaderGuard; table: Table[string, string]): Headers = + let headers = newHeaders(guard) for k, v in table: let k = k.toHeaderCase() headers.table.withValue(k, vs): @@ -248,6 +257,12 @@ func newHeaders*(table: Table[string, string]): Headers = headers.table[k] = @[v] return headers +func newHeaders(obj = none(HeadersInit)): JSResult[Headers] {.jsctor.} = + let headers = Headers(guard: hgNone) + if obj.isSome: + ?headers.fill(obj.get) + return ok(headers) + func clone*(headers: Headers): Headers = return Headers(table: headers.table) @@ -258,24 +273,33 @@ proc add*(headers: Headers; k: string; v: sink string) = do: headers.table[k] = @[v] -proc `[]=`*(headers: Headers; k: static string; v: string) = - const k = k.toHeaderCase() +proc `[]=`*(headers: Headers; k: string; v: sink string) = + let k = k.toHeaderCase() headers.table[k] = @[v] -func `[]`*(headers: Headers; k: static string): var string = - const k = k.toHeaderCase() +func `[]`*(headers: Headers; k: string): var string = + let k = k.toHeaderCase() return headers.table[k][0] -func contains*(headers: Headers; k: static string): bool = - const k = k.toHeaderCase() - return k in headers.table +func contains*(headers: Headers; k: string): bool = + return k.toHeaderCase() in headers.table -func getOrDefault*(headers: Headers; k: static string; default = ""): string = - const k = k.toHeaderCase() +func getOrDefault*(headers: Headers; k: string; default = ""): string = + let k = k.toHeaderCase() headers.table.withValue(k, p): return p[][0] do: return default +func getAllCommaSplit*(headers: Headers; k: string): seq[string] = + headers.table.withValue(k, p): + return p[].join(",").split(',') + return @[] + +func getAllNoComma*(headers: Headers; k: string): seq[string] = + headers.table.withValue(k, p): + return p[] + return @[] + proc addHeadersModule*(ctx: JSContext) = ctx.registerType(Headers) diff --git a/src/server/loader.nim b/src/server/loader.nim index 5f993ccc..92a10fd6 100644 --- a/src/server/loader.nim +++ b/src/server/loader.nim @@ -804,7 +804,7 @@ proc setupEnv(cpath: CGIPath; request: Request; contentLen: int; prevURL: URL; putEnv("REQUEST_URI", cpath.requestURI) putEnv("REQUEST_METHOD", $request.httpMethod) var headers = "" - for k, v in request.headers: + for k, v in request.headers.allPairs: headers &= k & ": " & v & "\r\n" putEnv("REQUEST_HEADERS", headers) if prevURL != nil: @@ -971,7 +971,7 @@ proc loadCGI(ctx: var LoaderContext; client: ClientHandle; handle: InputHandle; ostreamOut2.sclose() # close write if request.body.t != rbtNone: istream.sclose() # close read - handle.parser = HeaderParser(headers: newHeaders()) + handle.parser = HeaderParser(headers: newHeaders(hgResponse)) handle.stream = istreamOut case request.body.t of rbtString: @@ -1015,7 +1015,7 @@ proc loadStream(ctx: var LoaderContext; client: ClientHandle; case ctx.sendResult(handle, 0) of pbrDone: discard of pbrUnregister: return - case ctx.sendStatus(handle, 200, newHeaders()) + case ctx.sendStatus(handle, 200, newHeaders(hgResponse)) of pbrDone: discard of pbrUnregister: return let ps = client.passedFdMap[i].ps @@ -1049,7 +1049,7 @@ proc loadFromCache(ctx: var LoaderContext; client: ClientHandle; client.cacheMap.del(n) ctx.close(handle) return - case ctx.sendStatus(handle, 200, newHeaders()) + case ctx.sendStatus(handle, 200, newHeaders(hgResponse)) of pbrDone: discard of pbrUnregister: client.cacheMap.del(n) @@ -1070,7 +1070,7 @@ proc loadDataSend(ctx: var LoaderContext; handle: InputHandle; s, ct: string) = of pbrUnregister: ctx.close(handle) return - case ctx.sendStatus(handle, 200, newHeaders({"Content-Type": ct})) + case ctx.sendStatus(handle, 200, newHeaders(hgResponse, {"Content-Type": ct})) of pbrDone: discard of pbrUnregister: ctx.close(handle) @@ -1309,11 +1309,11 @@ proc loadResource(ctx: var LoaderContext; client: ClientHandle; ctx.rejectHandle(handle, ceTooManyRewrites) proc setupRequestDefaults(request: Request; config: LoaderClientConfig) = - for k, v in config.defaultHeaders.table: - if k notin request.headers.table: - request.headers.table[k] = v + 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 "Cookie" notin request.headers.table: + if "Cookie" notin request.headers: let cookie = config.cookieJar.serialize(request.url) if cookie != "": request.headers["Cookie"] = cookie diff --git a/src/server/loaderiface.nim b/src/server/loaderiface.nim index 0afd8fde..ba6cf9f9 100644 --- a/src/server/loaderiface.nim +++ b/src/server/loaderiface.nim @@ -86,18 +86,16 @@ type insecureSslNoVerify*: bool proc getRedirect*(response: Response; request: Request): Request = - if "Location" in response.headers.table: - if response.status in 301u16..303u16 or response.status in 307u16..308u16: - let location = response.headers.table["Location"][0] - let url = parseURL(location, option(request.url)) - if url.isSome: - let status = response.status - if status == 303 and request.httpMethod notin {hmGet, hmHead} or - status == 301 or - status == 302 and request.httpMethod == hmPost: - return newRequest(url.get, hmGet) - else: - return newRequest(url.get, request.httpMethod, body = request.body) + if response.status in 301u16..303u16 or response.status in 307u16..308u16: + let location = response.headers.getOrDefault("Location") + let url = parseURL(location, option(request.url)) + if url.isSome: + let status = response.status + if status == 303 and request.httpMethod notin {hmGet, hmHead} or + status == 301 or + status == 302 and request.httpMethod == hmPost: + return newRequest(url.get, hmGet) + return newRequest(url.get, request.httpMethod, body = request.body) return nil template withPacketWriter(loader: FileLoader; w, body: untyped) = diff --git a/src/server/request.nim b/src/server/request.nim index 62dbc45c..31b4c117 100644 --- a/src/server/request.nim +++ b/src/server/request.nim @@ -1,5 +1,4 @@ import std/options -import std/tables import html/script import io/packetreader @@ -137,12 +136,7 @@ proc jsReferrer(this: JSRequest): string {.jsfget: "referrer".} = return $this.request.referrer return "" -iterator pairs*(headers: Headers): (string, string) = - for k, vs in headers.table: - for v in vs: - yield (k, v) - -func newRequest*(url: URL; httpMethod = hmGet; headers = newHeaders(); +func newRequest*(url: URL; httpMethod = hmGet; headers = newHeaders(hgRequest); body = RequestBody(); referrer: URL = nil; tocache = false): Request = return Request( url: url, @@ -216,7 +210,7 @@ var getAPIBaseURLImpl*: proc(ctx: JSContext): URL {.nimcall.} proc newRequest*(ctx: JSContext; resource: JSValueConst; init = RequestInit(window: JS_UNDEFINED)): JSResult[JSRequest] {.jsctor.} = - let headers = newHeaders(hgRequest) + var headers = newHeaders(hgRequest) var fallbackMode = opt(rmCors) var window = RequestWindow(t: rwtClient) var body = RequestBody() @@ -227,7 +221,7 @@ proc newRequest*(ctx: JSContext; resource: JSValueConst; if (var res: JSRequest; ctx.fromJS(resource, res).isSome): url = res.url httpMethod = res.request.httpMethod - headers.table = res.headers.table + headers[] = res.headers[] referrer = res.request.referrer credentials = res.credentialsMode body = res.request.body diff --git a/src/server/response.nim b/src/server/response.nim index 58fc0571..401ce030 100644 --- a/src/server/response.nim +++ b/src/server/response.nim @@ -1,6 +1,5 @@ import std/posix import std/strutils -import std/tables import chagashi/charset import chagashi/decoder @@ -104,16 +103,17 @@ proc close*(response: Response) = response.body = nil func getCharset*(this: Response; fallback: Charset): Charset = - this.headers.table.withValue("Content-Type", p): - let header = p[][0].toLowerAscii() + let header = this.headers.getOrDefault("Content-Type").toLowerAscii() + if header != "": let cs = header.getContentTypeAttr("charset").getCharset() if cs != CHARSET_UNKNOWN: return cs return fallback func getLongContentType*(this: Response; fallback: string): string = - this.headers.table.withValue("Content-Type", p): - return p[][0].toValidUTF8().toLowerAscii().strip() + let header = this.headers.getOrDefault("Content-Type") + if header != "": + return header.toValidUTF8().toLowerAscii().strip() # also use DefaultGuess for container, so that local mime.types cannot # override buffer mime.types return DefaultGuess.guessContentType(this.url.pathname, fallback) @@ -123,16 +123,15 @@ func getContentType*(this: Response; fallback = "application/octet-stream"): return this.getLongContentType(fallback).until(';') func getContentLength*(this: Response): int64 = - this.headers.table.withValue("Content-Length", p): - for x in p[]: - let u = parseUInt64(x.strip(), allowSign = false) - if u.isSome and u.get <= uint64(int64.high): - return int64(u.get) + let x = this.headers.getOrDefault("Content-Length") + let u = parseUInt64(x.strip(), allowSign = false) + if u.isSome and u.get <= uint64(int64.high): + return int64(u.get) return -1 func getReferrerPolicy*(this: Response): Option[ReferrerPolicy] = - this.headers.table.withValue("Referrer-Policy", p): - return strictParseEnum[ReferrerPolicy](p[][0]) + let header = this.headers.getOrDefault("Referrer-Policy") + return strictParseEnum[ReferrerPolicy](header) proc resume*(response: Response) = response.resumeFun(response.outputId) |