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 }