diff options
Diffstat (limited to 'src/html/formdata.nim')
-rw-r--r-- | src/html/formdata.nim | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/src/html/formdata.nim b/src/html/formdata.nim new file mode 100644 index 00000000..bbf9a843 --- /dev/null +++ b/src/html/formdata.nim @@ -0,0 +1,177 @@ +import std/base64 +import std/streams + +import html/catom +import html/dom +import html/enums +import js/domexception +import js/javascript +import js/tojs +import types/blob +import types/formdata +import utils/twtstr + +import chame/tags + +proc constructEntryList*(form: HTMLFormElement, submitter: Element = nil, + encoding = "UTF-8"): seq[FormDataEntry] + +proc generateBoundary(): string = + let urandom = newFileStream("/dev/urandom") + let s = urandom.readStr(32) + urandom.close() + # 32 * 4 / 3 (padded) = 44 + prefix string is 22 bytes = 66 bytes + return "----WebKitFormBoundary" & base64.encode(s) + +proc newFormData0*(): FormData = + return FormData(boundary: generateBoundary()) + +proc newFormData*(form: HTMLFormElement = nil, + submitter: HTMLElement = nil): DOMResult[FormData] {.jsctor.} = + let this = newFormData0() + if form != nil: + if submitter != nil: + if not submitter.isSubmitButton(): + return errDOMException("Submitter must be a submit button", + "InvalidStateError") + if FormAssociatedElement(submitter).form != form: + return errDOMException("Submitter's form owner is not form", + "InvalidStateError") + if not form.constructingEntryList: + this.entries = constructEntryList(form, submitter) + return ok(this) + +#TODO filename should not be allowed for string entries +# in other words, this should be an overloaded function, not just an or type +proc append*[T: string|Blob](this: FormData, name: string, value: T, + filename = opt(string)) {.jsfunc.} = + when T is Blob: + let filename = if filename.isSome: + filename.get + elif value of WebFile: + WebFile(value).name + else: + "blob" + this.entries.add(FormDataEntry( + name: name, + isstr: false, + value: value, + filename: filename + )) + else: # string + this.entries.add(FormDataEntry( + name: name, + isstr: true, + svalue: value + )) + +proc delete(this: FormData, name: string) {.jsfunc.} = + for i in countdown(this.entries.high, 0): + if this.entries[i].name == name: + this.entries.delete(i) + +proc get(ctx: JSContext, this: FormData, name: string): JSValue {.jsfunc.} = + for entry in this.entries: + if entry.name == name: + if entry.isstr: + return toJS(ctx, entry.svalue) + else: + return toJS(ctx, entry.value) + return JS_NULL + +proc getAll(ctx: JSContext, this: FormData, name: string): seq[JSValue] + {.jsfunc.} = + for entry in this.entries: + if entry.name == name: + if entry.isstr: + result.add(toJS(ctx, entry.svalue)) + else: + result.add(toJS(ctx, entry.value)) + +proc add(list: var seq[FormDataEntry], entry: tuple[name, value: string]) = + list.add(FormDataEntry( + name: entry.name, + isstr: true, + svalue: entry.value + )) + +func toNameValuePairs*(list: seq[FormDataEntry]): + seq[tuple[name, value: string]] = + for entry in list: + if entry.isstr: + result.add((entry.name, entry.svalue)) + else: + result.add((entry.name, entry.name)) + +# https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set +# Warning: we skip the first "constructing entry list" check; the caller must +# do it. +proc constructEntryList*(form: HTMLFormElement, submitter: Element = nil, + encoding = "UTF-8"): seq[FormDataEntry] = + assert not form.constructingEntryList + form.constructingEntryList = true + var entrylist: seq[FormDataEntry] = @[] + for field in form.controls: + if field.findAncestor({TAG_DATALIST}) != nil or + field.attrb(atDisabled) or + field.isButton() and Element(field) != submitter: + continue + if field of HTMLInputElement: + let field = HTMLInputElement(field) + if field.inputType in {INPUT_CHECKBOX, INPUT_RADIO} and not field.checked: + continue + if field.inputType == INPUT_IMAGE: + var name = field.attr(atName) + if name != "": + name &= '.' + entrylist.add((name & 'x', $field.xcoord)) + entrylist.add((name & 'y', $field.ycoord)) + continue + #TODO custom elements + let name = field.attr(atName) + if name == "": + continue + if field of HTMLSelectElement: + let field = HTMLSelectElement(field) + for option in field.options: + if option.selected and not option.isDisabled: + entrylist.add((name, option.value)) + elif field of HTMLInputElement: + let field = HTMLInputElement(field) + case field.inputType + of INPUT_CHECKBOX, INPUT_RADIO: + let v = field.attr(atValue) + let value = if v != "": + v + else: + "on" + entrylist.add((name, value)) + of INPUT_FILE: + #TODO file + discard + of INPUT_HIDDEN: + if name.equalsIgnoreCase("_charset_"): + entrylist.add((name, encoding)) + else: + entrylist.add((name, field.value)) + else: + entrylist.add((name, field.value)) + elif field of HTMLButtonElement: + entrylist.add((name, HTMLButtonElement(field).value)) + elif field of HTMLTextAreaElement: + entrylist.add((name, HTMLTextAreaElement(field).value)) + else: + assert false, "Tag type " & $field.tagType & + " not accounted for in constructEntryList" + if field of HTMLTextAreaElement or + field of HTMLInputElement and + HTMLInputElement(field).inputType in AutoDirInput: + let dirname = field.attr(atDirname) + if dirname != "": + let dir = "ltr" #TODO bidi + entrylist.add((dirname, dir)) + form.constructingEntryList = false + return entrylist + +proc addFormDataModule*(ctx: JSContext) = + ctx.registerType(FormData) |