about summary refs log tree commit diff stats
path: root/src/io/http.nim
blob: cda70bf1626f21466ba69d351c9ef3fe81d170ea (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import options
import streams
import strutils
import tables

import bindings/curl
import io/request
import io/serialize
import types/url
import utils/twtstr

type
  HeaderResult* = ref object
    statusline: bool
    headers: HeaderList
    curl: CURL
    ostream: Stream
    request: Request

template setopt(curl: CURL, opt: CURLoption, arg: typed) =
  discard curl_easy_setopt(curl, opt, arg)

template setopt(curl: CURL, opt: CURLoption, arg: string) =
  discard curl_easy_setopt(curl, opt, cstring(arg))

template getinfo(curl: CURL, info: CURLINFO, arg: typed) =
  discard curl_easy_getinfo(curl, info, arg)

proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t, userdata: pointer): csize_t {.cdecl.} =
  var line = newString(nitems)
  for i in 0..<nitems:
    line[i] = p[i]

  let headers = cast[HeaderResult](userdata)
  if not headers.statusline:
    headers.statusline = true
    headers.ostream.swrite(int(CURLE_OK))
    var status: int
    headers.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
    headers.ostream.swrite(status)
    return nitems

  let k = line.until(':')

  if k.len == line.len:
    # empty line (last, before body) or invalid (=> error)
    headers.ostream.swrite(headers.headers.getOrDefault("Content-Type").until(';'))
    var urlp: cstring
    headers.curl.getinfo(CURLINFO_REDIRECT_URL, addr urlp)
    if "Location" in headers.headers.table:
      let location = headers.headers.table["Location"][0]
      headers.ostream.swrite(parseUrl(location, some(headers.request.url)))
    else:
      headers.ostream.swrite(none(Url))
    return nitems

  let v = line.substr(k.len + 1).strip()
  headers.headers.add(k, v)
  return nitems

proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, userdata: pointer): csize_t {.cdecl.} =
  var s = newString(nmemb)
  for i in 0..<nmemb:
    s[i] = p[i]
  let stream = cast[Stream](userdata)
  if nmemb > 0:
    stream.swrite(s)
    stream.flush()
  return nmemb

proc loadHttp*(request: Request, ostream: Stream) =
  let curl = curl_easy_init()

  if curl == nil: return # fail

  let surl = request.url.serialize()
  curl.setopt(CURLOPT_URL, surl)

  curl.setopt(CURLOPT_WRITEDATA, ostream)
  curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)

  let headerres = HeaderResult(headers: newHeaderList(), curl: curl, ostream: ostream, request: request)
  curl.setopt(CURLOPT_HEADERDATA, headerres)
  curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader)

  var mime: curl_mime = nil

  case request.httpmethod
  of HTTP_GET: curl.setopt(CURLOPT_HTTPGET, 1)
  of HTTP_POST:
    curl.setopt(CURLOPT_POST, 1)
    if request.multipart.issome:
      mime = curl_mime_init(curl)
      if mime == nil: return # fail
      for entry in request.multipart.get.content:
        let part = curl_mime_addpart(mime)
        if part == nil: return # fail
        curl_mime_name(part, cstring(entry.name))
        if entry.isFile:
          if entry.isStream:
            curl_mime_filedata(part, cstring(entry.filename))
          else:
            let fd = readFile(entry.filename)
            curl_mime_data(part, cstring(fd), csize_t(fd.len))
          # may be overridden by curl_mime_filedata, so set it here
          curl_mime_filename(part, cstring(entry.filename))
        else:
          curl_mime_data(part, cstring(entry.content), csize_t(entry.content.len))
      curl.setopt(CURLOPT_MIMEPOST, mime)
    elif request.body.issome:
      curl.setopt(CURLOPT_POSTFIELDS, cstring(request.body.get))
      curl.setopt(CURLOPT_POSTFIELDSIZE, request.body.get.len)
  else: discard #TODO

  var slist: curl_slist = nil
  for k, v in request.headers:
    let header = k & ": " & v
    slist = curl_slist_append(slist, cstring(header))
  if slist != nil:
    curl.setopt(CURLOPT_HTTPHEADER, slist)

  let res = curl_easy_perform(curl)
  if res == CURLE_OK:
    ostream.swrite("")
    ostream.flush()
  else:
    ostream.swrite(int(res))
    ostream.flush()

  curl_easy_cleanup(curl)
  if mime != nil:
    curl_mime_free(mime)
  if slist != nil:
    curl_slist_free_all(slist)