001 // vim: set ft=cpp:
002 
003 #include "::/Adam/Net/Socket"
004 #include "::/Adam/Net/UrlParse"
005 
006 #define HTTP_ECONNECT   (-101)
007 #define HTTP_EPROTOCOL  (-102)
008 #define HTTP_EREQUEST   (-103)
009 #define HTTP_EREDIRECT  (-104)
010 #define HTTP_EEOF       (-105)
011 #define HTTP_ECONTENTLENGTH (-106)
012 
013 #define HTTP_MAX_REDIRECTS  5
014 #define HTTP_USER_AGENT "SnailNet (     )"
015 
016 /**
017  * @param len_out (required) requires the content length, or -1 if unspecified
018  * @return socket (>= 0) on success, error code on failure
019  */
020 I64 HttpOpenGet(U8* host, U16 port = 0, U8* path, I64* len_out,
021     I64 allowed_redirects = HTTP_MAX_REDIRECTS) {
022   U8 line[256];
023   I64 error = 0;
024 
025   if (!port)
026     port = 80;
027 
028   // Should this be done here though?
029   if (*path == 0)
030     path = "/";
031 
032   //"Connect(%s:%d)\n", host, port;
033   I64 sock = create_connection(host, port);
034   //"create_connection: %d\n", sock;
035   if (sock >= 0) {
036     StrPrint(line, "GET %s HTTP/1.0\r\n", path);
037     sendString(sock, line, 0);
038     StrPrint(line, "Host: %s\r\n", host);
039     sendString(sock, line, 0);
040     sendString(sock, "User-Agent: " HTTP_USER_AGENT "\r\n", 0);
041     sendString(sock, "\r\n", 0);
042 
043     Bool haveHTTP = FALSE;
044     U8* location = NULL;
045 
046     I64 code = -1;
047 
048     *len_out = -1;
049 
050     while (1) {
051       error = recvLine(sock, line, sizeof(line), 0);
052 
053       if (error < 0) {
054         break;
055       }
056       else if (error == 0) {
057         if (!haveHTTP)
058           error = HTTP_EPROTOCOL;
059         break;
060       }
061 
062       U8* delim;
063 
064       //"%s\n", line;
065       if (!haveHTTP) {
066         delim = StrFirstOcc(line, " ");
067         if (delim && StrNCmp(line, "HTTP/", 5) == 0) {
068           code = Str2I64(delim + 1);
069 
070           if (code >= 200 && code <= 399) {
071             haveHTTP = TRUE;
072           }
073           else {
074             error = HTTP_EREQUEST;
075             break;
076           }
077         }
078         else {
079           error = HTTP_EREQUEST;
080           break;
081         }
082       }
083       else {
084         delim = StrFirstOcc(line, ":");
085 
086         if (!delim) {
087           error = HTTP_EPROTOCOL;
088           break;
089         }
090 
091         *delim = 0;
092 
093         do { delim++; }
094         while (*delim == ' ');
095 
096         //"%s=%s\n", line, delim;
097         if (!StrCmp(line, "Content-Length")) {
098           StrScan(delim, "%d", len_out);
099         }
100         else if (!StrCmp(line, "Location")) {
101           // This will leak on malformed response
102           location = StrNew(delim);
103         }
104       }
105     }
106 
107     // HTTP Code 3xx -- Redirection
108     if (!error && code >= 300 && code <= 399) {
109       if (allowed_redirects > 0) {
110         CUrl curl;
111         UrlInit(&curl);
112 
113         if (UrlParse(location, &curl)) {
114           if (!StrCmp(curl.protocol, "http")) {
115             close(sock);
116             sock = HttpOpenGet(curl.host, curl.port, curl.path, len_out, allowed_redirects - 1);
117 
118             if (sock < 0)
119               error = sock;
120           }
121           else
122             error = HTTP_EPROTOCOL;
123         }
124         else
125           error = HTTP_EREDIRECT;
126 
127         UrlFree(&curl);
128       }
129       else {
130         error = HTTP_EREDIRECT;
131       }
132     }
133 
134     Free(location);
135   }
136   else
137     error = HTTP_ECONNECT;
138 
139   if (error) {
140     close(sock);
141     return error;
142   }
143   else {
144     return sock;
145   }
146 }
147 
148 I64 HttpGet(U8* host, U16 port = 0, U8* path, U8** data_out = NULL, I64* len_out = NULL) {
149   I64 error = 0;
150   I64 len = 0;
151   I64 sock = HttpOpenGet(host, port, path, &len);
152 
153   if (sock > 0) {
154     if (len >= 0) {
155       U8* data = MAlloc(1 + len);
156       I64 total = 0;
157 
158       while (total < len) {
159         I64 step = len - total;
160 
161         if (step > 1024)
162           step = 1024;
163 
164         I64 got = recv(sock, data + total, step, 0);
165 
166         if (got <= 0) {
167           error = HTTP_EEOF;
168           break;
169         }
170 
171         total += got;
172       }
173 
174       if (error) {
175         Free(data);
176       }
177       else {
178         if (data_out) {
179           data[total] = 0;
180           *data_out = data;
181         }
182 
183         if (len_out)
184           *len_out = len;
185       }
186     }
187     else
188       // We currently don't handle dynamic-length HTTP responses
189       error = HTTP_ECONTENTLENGTH;
190 
191     close(sock);
192   }
193   else
194     error = sock;
195 
196   return error;
197 }