diff options
author | bptato <nincsnevem662@gmail.com> | 2024-05-20 18:12:09 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-05-20 18:12:09 +0200 |
commit | 7c4bb940410c8f5ad59e1d21d5565364a9a0cd71 (patch) | |
tree | d46d682ce9d4f308232c961985d8411c2a70197c /src/loader | |
parent | 723613b0a02605dbf715d74c70b9ec29f1092c76 (diff) | |
download | chawan-7c4bb940410c8f5ad59e1d21d5565364a9a0cd71.tar.gz |
html: improve Request, derive Client from Window
* make Client an instance of Window (for less special casing) * misc work on Request & fetch * improve origin comparison (opaque origins of same URLs are now considered the same)
Diffstat (limited to 'src/loader')
-rw-r--r-- | src/loader/cgi.nim | 2 | ||||
-rw-r--r-- | src/loader/loader.nim | 22 | ||||
-rw-r--r-- | src/loader/request.nim | 261 | ||||
-rw-r--r-- | src/loader/response.nim | 12 |
4 files changed, 149 insertions, 148 deletions
diff --git a/src/loader/cgi.nim b/src/loader/cgi.nim index 347855ac..f3c5b1e3 100644 --- a/src/loader/cgi.nim +++ b/src/loader/cgi.nim @@ -41,7 +41,7 @@ proc setupEnv(cmd, scriptName, pathInfo, requestURI, myDir: string; putEnv("PATH_INFO", pathInfo) if url.query.isSome: putEnv("QUERY_STRING", url.query.get) - if request.httpMethod == HTTP_POST: + if request.httpMethod == hmPost: if request.multipart.isSome: putEnv("CONTENT_TYPE", request.multipart.get.getContentType()) else: diff --git a/src/loader/loader.nim b/src/loader/loader.nim index c378523b..8bc23c16 100644 --- a/src/loader/loader.nim +++ b/src/loader/loader.nim @@ -854,18 +854,18 @@ proc getRedirect*(response: Response; request: Request): Request = let location = response.headers.table["Location"][0] let url = parseURL(location, option(request.url)) if url.isSome: - if (response.status == 303 and - request.httpMethod notin {HTTP_GET, HTTP_HEAD}) or - (response.status == 301 or response.status == 302 and - request.httpMethod == HTTP_POST): - return newRequest(url.get, HTTP_GET, - mode = request.mode, credentialsMode = request.credentialsMode, - destination = request.destination) + 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, multipart = request.multipart, - mode = request.mode, credentialsMode = request.credentialsMode, - destination = request.destination) + return newRequest( + url.get, + request.httpMethod, + body = request.body, + multipart = request.multipart + ) return nil template withLoaderPacketWriter(stream: SocketStream; loader: FileLoader; diff --git a/src/loader/request.nim b/src/loader/request.nim index 8eeb17b8..1398d25f 100644 --- a/src/loader/request.nim +++ b/src/loader/request.nim @@ -3,9 +3,10 @@ import std/strutils import std/tables import bindings/quickjs -import js/jserror +import html/script import js/fromjs import js/javascript +import js/jserror import js/jstypes import loader/headers import types/blob @@ -15,82 +16,83 @@ import types/url type HttpMethod* = enum - HTTP_GET = "GET" - HTTP_CONNECT = "CONNECT" - HTTP_DELETE = "DELETE" - HTTP_HEAD = "HEAD" - HTTP_OPTIONS = "OPTIONS" - HTTP_PATCH = "PATCH" - HTTP_POST = "POST" - HTTP_PUT = "PUT" - HTTP_TRACE = "TRACE" + hmGet = "GET" + hmConnect = "CONNECT" + hmDelete = "DELETE" + hmHead = "HEAD" + hmOptions = "OPTIONS" + hmPatch = "PATCH" + hmPost = "POST" + hmPut = "PUT" + hmTrace = "TRACE" RequestMode* = enum - NO_CORS = "no-cors" - SAME_ORIGIN = "same-origin" - CORS = "cors" - NAVIGATE = "navigate" - WEBSOCKET = "websocket" - - RequestDestination* = enum - NO_DESTINATION = "" - AUDIO = "audio" - AUDIOWORKLET = "audioworklet" - DOCUMENT = "document" - EMBED = "embed" - FONT = "font" - FRAME = "frame" - IFRAME = "iframe" - IMAGE = "image" - JSON = "json" - MANIFEST = "manifest" - OBJECT = "object" - PAINTWORKLET = "paintworklet" - REPORT = "report" - SCRIPT = "script" - SERVICEWORKER = "serviceworker" - SHAREDWORKER = "sharedworker" - STYLE = "style" - TRACK = "track" - WORKER = "worker" - XSLT = "xslt" - - CredentialsMode* = enum - SAME_ORIGIN = "same-origin" - OMIT = "omit" - INCLUDE = "include" + rmNoCors = "no-cors" + rmSameOrigin = "same-origin" + rmCors = "cors" + rmNavigate = "navigate" + rmWebsocket = "websocket" CORSAttribute* = enum - NO_CORS = "no-cors" - ANONYMOUS = "anonymous" - USE_CREDENTIALS = "use-credentials" + caNoCors = "no-cors" + caAnonymous = "anonymous" + caUseCredentials = "use-credentials" type - Request* = ref RequestObj - RequestObj* = object + RequestOriginType* = enum + rotClient, rotOrigin + + RequestOrigin* = object + case t*: RequestOriginType + of rotClient: discard + of rotOrigin: + origin*: Origin + + RequestWindowType* = enum + rwtClient, rwtNoWindow, rwtWindow + + RequestWindow* = object + case t*: RequestWindowType + of rwtClient, rwtNoWindow: discard + of rwtWindow: + window*: EnvironmentSettings + + Request* = ref object httpMethod*: HttpMethod url*: URL - headers* {.jsget.}: Headers + headers*: Headers body*: Option[string] multipart*: Option[FormData] referrer*: URL - mode* {.jsget.}: RequestMode - destination* {.jsget.}: RequestDestination - credentialsMode* {.jsget.}: CredentialsMode proxy*: URL #TODO do something with this # when set to true, the loader will not write data from the body (not # headers!) into the output until a resume is received. suspended*: bool -jsDestructor(Request) + JSRequest* = ref object + request*: Request + mode* {.jsget.}: RequestMode + destination* {.jsget.}: RequestDestination + credentialsMode* {.jsget.}: CredentialsMode + origin*: RequestOrigin + window*: RequestWindow + client*: Option[EnvironmentSettings] + +jsDestructor(JSRequest) -proc js_url(this: Request): string {.jsfget: "url".} = +func headers(this: JSRequest): Headers {.jsfget.} = + return this.request.headers + +func url(this: JSRequest): URL = + return this.request.url + +proc jsUrl(this: JSRequest): string {.jsfget: "url".} = return $this.url #TODO pretty sure this is incorrect -proc js_referrer(this: Request): string {.jsfget: "referrer".} = - if this.referrer != nil: - return $this.referrer +proc jsReferrer(this: JSRequest): string {.jsfget: "referrer".} = + if this.request.referrer != nil: + return $this.request.referrer return "" iterator pairs*(headers: Headers): (string, string) = @@ -98,10 +100,8 @@ iterator pairs*(headers: Headers): (string, string) = for v in vs: yield (k, v) -func newRequest*(url: URL; httpMethod = HTTP_GET; headers = newHeaders(); - body = none(string); multipart = none(FormData); mode = RequestMode.NO_CORS; - credentialsMode = CredentialsMode.SAME_ORIGIN; - destination = RequestDestination.NO_DESTINATION; proxy: URL = nil; +func newRequest*(url: URL; httpMethod = hmGet; headers = newHeaders(); + body = none(string); multipart = none(FormData); proxy: URL = nil; referrer: URL = nil; suspended = false): Request = return Request( url: url, @@ -109,18 +109,14 @@ func newRequest*(url: URL; httpMethod = HTTP_GET; headers = newHeaders(); headers: headers, body: body, multipart: multipart, - mode: mode, - credentialsMode: credentialsMode, - destination: destination, referrer: referrer, proxy: proxy, suspended: suspended ) -func newRequest*(url: URL; httpMethod = HTTP_GET; +func newRequest*(url: URL; httpMethod = hmGet; headers: seq[(string, string)] = @[]; body = none(string); - multipart = none(FormData); mode = RequestMode.NO_CORS; proxy: URL = nil): - Request = + multipart = none(FormData); proxy: URL = nil): Request = let hl = newHeaders() for pair in headers: let (k, v) = pair @@ -131,43 +127,39 @@ func newRequest*(url: URL; httpMethod = HTTP_GET; hl, body, multipart, - mode, proxy = proxy ) func createPotentialCORSRequest*(url: URL; destination: RequestDestination; - cors: CORSAttribute; fallbackFlag = false): Request = - var mode = if cors == NO_CORS: - RequestMode.NO_CORS + cors: CORSAttribute; fallbackFlag = false): JSRequest = + var mode = if cors == caNoCors: + rmNoCors else: - RequestMode.CORS - if fallbackFlag and mode == NO_CORS: - mode = SAME_ORIGIN - let credentialsMode = if cors == ANONYMOUS: - CredentialsMode.SAME_ORIGIN - else: CredentialsMode.INCLUDE - return newRequest( - url, - destination = destination, - mode = mode, - credentialsMode = credentialsMode + rmCors + if fallbackFlag and mode == rmNoCors: + mode = rmSameOrigin + let credentialsMode = if cors == caAnonymous: cmSameOrigin else: cmInclude + return JSRequest( + request: newRequest(url), + destination: destination, + mode: mode, + credentialsMode: credentialsMode ) type BodyInitType = enum - BODY_INIT_BLOB, BODY_INIT_FORM_DATA, BODY_INIT_URL_SEARCH_PARAMS, - BODY_INIT_STRING + bitBlob, bitFormData, bitUrlSearchParams, bitString BodyInit = object #TODO ReadableStream, BufferSource case t: BodyInitType - of BODY_INIT_BLOB: + of bitBlob: blob: Blob - of BODY_INIT_FORM_DATA: + of bitFormData: formData: FormData - of BODY_INIT_URL_SEARCH_PARAMS: + of bitUrlSearchParams: searchParams: URLSearchParams - of BODY_INIT_STRING: + of bitString: str: string RequestInit* = object of JSDict @@ -180,6 +172,7 @@ type credentials: Option[CredentialsMode] proxyUrl: URL mode: Option[RequestMode] + window: Option[JSValue] proc fromJSBodyInit(ctx: JSContext; val: JSValue): JSResult[BodyInit] = if JS_IsUndefined(val) or JS_IsNull(val): @@ -187,67 +180,72 @@ proc fromJSBodyInit(ctx: JSContext; val: JSValue): JSResult[BodyInit] = block formData: let x = fromJS[FormData](ctx, val) if x.isSome: - return ok(BodyInit(t: BODY_INIT_FORM_DATA, formData: x.get)) + return ok(BodyInit(t: bitFormData, formData: x.get)) block blob: let x = fromJS[Blob](ctx, val) if x.isSome: - return ok(BodyInit(t: BODY_INIT_BLOB, blob: x.get)) + return ok(BodyInit(t: bitBlob, blob: x.get)) block searchParams: let x = fromJS[URLSearchParams](ctx, val) if x.isSome: - return ok(BodyInit(t: BODY_INIT_URL_SEARCH_PARAMS, searchParams: x.get)) + return ok(BodyInit(t: bitUrlSearchParams, searchParams: x.get)) block str: let x = fromJS[string](ctx, val) if x.isSome: - return ok(BodyInit(t: BODY_INIT_STRING, str: x.get)) + return ok(BodyInit(t: bitString, str: x.get)) return err(newTypeError("Invalid body init type")) -func newRequest*[T: string|Request](ctx: JSContext; resource: T; - init = none(RequestInit)): JSResult[Request] {.jsctor.} = +func newRequest*[T: string|JSRequest](ctx: JSContext; resource: T; + init = none(RequestInit)): JSResult[JSRequest] {.jsctor.} = + defer: + if init.isSome and init.get.window.isSome: + JS_FreeValue(ctx, init.get.window.get) when T is string: let url = ?newURL(resource) if url.username != "" or url.password != "": return err(newTypeError("Input URL contains a username or password")) - var httpMethod = HTTP_GET + var httpMethod = hmGet var headers = newHeaders() let referrer: URL = nil - var credentials = CredentialsMode.SAME_ORIGIN + var credentials = cmSameOrigin var body: Option[string] var multipart: Option[FormData] var proxyUrl: URL #TODO? - let fallbackMode = opt(RequestMode.CORS) + let fallbackMode = opt(rmCors) + var window = RequestWindow(t: rwtClient) else: let url = resource.url - var httpMethod = resource.httpMethod + var httpMethod = resource.request.httpMethod var headers = resource.headers.clone() - let referrer = resource.referrer + let referrer = resource.request.referrer var credentials = resource.credentialsMode - var body = resource.body - var multipart = resource.multipart - var proxyUrl = resource.proxy #TODO? + var body = resource.request.body + var multipart = resource.request.multipart + var proxyUrl = resource.request.proxy #TODO? let fallbackMode = none(RequestMode) - #TODO window - var mode = fallbackMode.get(RequestMode.NO_CORS) - let destination = NO_DESTINATION + var window = resource.window + var mode = fallbackMode.get(rmNoCors) + let destination = rdNone #TODO origin, window - if init.isSome: - if mode == RequestMode.NAVIGATE: - mode = RequestMode.SAME_ORIGIN + if init.isSome: #TODO spec wants us to check if it's "not empty"... + let init = init.get + if init.window.isSome: + if not JS_IsNull(init.window.get): + return errTypeError("Expected window to be null") + window = RequestWindow(t: rwtNoWindow) + if mode == rmNavigate: + mode = rmSameOrigin #TODO flags? #TODO referrer - let init = init.get httpMethod = init.`method` if init.body.isSome: let ibody = init.body.get case ibody.t - of BODY_INIT_FORM_DATA: - multipart = some(ibody.formData) - of BODY_INIT_STRING: - body = some(ibody.str) - else: - discard #TODO - if httpMethod in {HTTP_GET, HTTP_HEAD}: - return err(newTypeError("HEAD or GET Request cannot have a body.")) + of bitFormData: multipart = some(ibody.formData) + of bitString: body = some(ibody.str) + else: discard #TODO + if httpMethod in {hmGet, hmHead}: + return errTypeError("HEAD or GET Request cannot have a body.") if init.headers.isSome: headers.fill(init.headers.get) if init.credentials.isSome: @@ -256,25 +254,28 @@ func newRequest*[T: string|Request](ctx: JSContext; resource: T; mode = init.mode.get #TODO find a standard compatible way to implement this proxyUrl = init.proxyUrl - return ok(Request( - url: url, - httpMethod: httpMethod, - headers: headers, - body: body, - multipart: multipart, + return ok(JSRequest( + request: newRequest( + url, + httpMethod, + headers, + body, + multipart, + proxy = proxyUrl, + referrer = referrer + ), mode: mode, credentialsMode: credentials, destination: destination, - proxy: proxyUrl, - referrer: referrer + window: window )) func credentialsMode*(attribute: CORSAttribute): CredentialsMode = case attribute - of NO_CORS, ANONYMOUS: - return SAME_ORIGIN - of USE_CREDENTIALS: - return INCLUDE + of caNoCors, caAnonymous: + return cmSameOrigin + of caUseCredentials: + return cmInclude proc addRequestModule*(ctx: JSContext) = - ctx.registerType(Request) + ctx.registerType(JSRequest, name = "Request") diff --git a/src/loader/response.nim b/src/loader/response.nim index 63b07cf1..56e955a3 100644 --- a/src/loader/response.nim +++ b/src/loader/response.nim @@ -28,11 +28,11 @@ type #TODO fully implement headers guards HeadersGuard* = enum - GUARD_IMMUTABLE = "immutable" - GUARD_REQUEST = "request" - GUARD_REQUEST_NO_CORS = "request-no-cors" - GUARD_RESPONSE = "response" - GUARD_NONE = "none" + hgImmutable = "immutable" + hgRequest = "request" + hgRequestNoCors = "request-no-cors" + hgResponse = "response" + hgNone = "none" Response* = ref object responseType* {.jsget: "type".}: ResponseType @@ -67,7 +67,7 @@ func makeNetworkError*(): Response {.jsstfunc: "Response:error".} = responseType: TYPE_ERROR, status: 0, headers: newHeaders(), - headersGuard: GUARD_IMMUTABLE + headersGuard: hgImmutable ) func sok(response: Response): bool {.jsfget: "ok".} = |