1 /*
2  * Copyright (C) 2001-2012 Jacek Sieka, arnetheduck on gmail point com
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include "stdinc.h"
20 
21 #include "HttpConnection.h"
22 
23 #include "format.h"
24 #include "SettingsManager.h"
25 #include "version.h"
26 
27 namespace dcpp {
28 
29 static const std::string CORAL_SUFFIX = ".nyud.net";
30 
31 /**
32  * Downloads a file and returns it as a string
33  * @todo Report exceptions
34  * @todo Abort download
35  * @param aUrl Full URL of file
36  * @return A string with the content, or empty if download failed
37  */
downloadFile(const string & aUrl)38 void HttpConnection::downloadFile(const string& aUrl) {
39     dcassert(Util::findSubString(aUrl, "http://") == 0);
40     currentUrl = aUrl;
41     // Trim spaces
42     while(currentUrl[0] == ' ')
43         currentUrl.erase(0, 1);
44     while(currentUrl[currentUrl.length() - 1] == ' ') {
45         currentUrl.erase(currentUrl.length()-1);
46     }
47     // reset all settings (as in constructor), moved here from onLine(302) because ok was not reset properly
48     moved302 = false;
49     ok = false;
50     size = -1;
51     // set download type
52     if(Util::stricmp(currentUrl.substr(currentUrl.size() - 4), ".bz2") == 0) {
53         fire(HttpConnectionListener::TypeBZ2(), this);
54     } else {
55         fire(HttpConnectionListener::TypeNormal(), this);
56     }
57 
58         string proto, query, fragment;
59     if(SETTING(HTTP_PROXY).empty()) {
60                 Util::decodeUrl(currentUrl, proto, server, port, file, query, fragment);
61         if(file.empty())
62             file = "/";
63     } else {
64                 Util::decodeUrl(SETTING(HTTP_PROXY), proto, server, port, file, query, fragment);
65         file = currentUrl;
66     }
67 
68     if(BOOLSETTING(CORAL) && coralizeState != CST_NOCORALIZE) {
69         if(server.length() > CORAL_SUFFIX.length() && server.compare(server.length() - CORAL_SUFFIX.length(), CORAL_SUFFIX.length(), CORAL_SUFFIX) !=0) {
70             server += CORAL_SUFFIX;
71         } else {
72             coralizeState = CST_NOCORALIZE;
73         }
74 
75     }
76 
77     if(port == 0)
78         port = 80;
79 
80     if(!socket) {
81         socket = BufferedSocket::getSocket(0x0a);
82     }
83     socket->addListener(this);
84     try {
85         socket->connect(server, port, false, false, false);
86     } catch(const Exception& e) {
87         fire(HttpConnectionListener::Failed(), this, e.getError() + " (" + currentUrl + ")");
88     }
89 }
90 
on(BufferedSocketListener::Connected)91 void HttpConnection::on(BufferedSocketListener::Connected) noexcept {
92     dcassert(socket);
93     socket->write("GET " + file + " HTTP/1.1\r\n");
94 
95     string sRemoteServer = server;
96     if(!SETTING(HTTP_PROXY).empty())
97     {
98         string tfile, proto, query, fragment;
99         uint16_t tport;
100         Util::decodeUrl(file, proto, sRemoteServer, tport, tfile, query, fragment);
101     }
102 
103 #ifdef WITH_DHT
104     if (sRemoteServer == "strongdc.sourceforge.net")
105         socket->write("User-Agent: StrongDC++ v2.42\r\n");
106     else
107         socket->write("User-Agent: " APPNAME " v" VERSIONSTRING "\r\n");
108 #else
109     socket->write("User-Agent: " APPNAME " v" VERSIONSTRING "\r\n");
110 #endif
111 
112     socket->write("Host: " + sRemoteServer + "\r\n");
113     socket->write("Connection: close\r\n"); // we'll only be doing one request
114     socket->write("Cache-Control: no-cache\r\n\r\n");
115     if (coralizeState == CST_DEFAULT) coralizeState = CST_CONNECTED;
116 }
117 
on(BufferedSocketListener::Line,const string & aLine)118 void HttpConnection::on(BufferedSocketListener::Line, const string& aLine) noexcept {
119     if(!ok) {
120         dcdebug("%s\n",aLine.c_str());
121         if(aLine.find("200") == string::npos) {
122             if(aLine.find("301") != string::npos || aLine.find("302") != string::npos){
123                 moved302 = true;
124             } else {
125                 socket->disconnect();
126                 socket->removeListener(this);
127                 BufferedSocket::putSocket(socket);
128                 socket = NULL;
129                 if(SETTING(CORAL) && coralizeState != CST_NOCORALIZE) {
130                     fire(HttpConnectionListener::Retried(), this, coralizeState == CST_CONNECTED);
131                     coralizeState = CST_NOCORALIZE;
132                     dcdebug("HTTP error with Coral, retrying : %s\n",currentUrl.c_str());
133                     downloadFile(currentUrl);
134                     return;
135                 }
136                 fire(HttpConnectionListener::Failed(), this, aLine + " (" + currentUrl + ")");
137                 coralizeState = CST_DEFAULT;
138                 return;
139             }
140         }
141         ok = true;
142     } else if(moved302 && Util::findSubString(aLine, "Location") != string::npos){
143         dcassert(socket);
144         socket->removeListener(this);
145         socket->disconnect();
146         BufferedSocket::putSocket(socket);
147         socket = NULL;
148 
149         string location302 = aLine.substr(10, aLine.length() - 11);
150         // make sure we can also handle redirects with relative paths
151         if(Util::strnicmp(location302.c_str(), "http://", 7) != 0) {
152             if(location302[0] == '/') {
153                 string proto, query, fragment;
154                 Util::decodeUrl(currentUrl, proto, server, port, file, query, fragment);
155                 string tmp = "http://" + server;
156                 if(port != 80)
157                     tmp += ':' + Util::toString(port);
158                 location302 = tmp + location302;
159             } else {
160                 string::size_type i = currentUrl.rfind('/');
161                 dcassert(i != string::npos);
162                 location302 = currentUrl.substr(0, i + 1) + location302;
163             }
164         }
165         if(location302 == currentUrl) {
166             fire(HttpConnectionListener::Failed(), this, str(F_("Endless redirection loop (%1%)") % currentUrl));
167             return;
168         }
169         fire(HttpConnectionListener::Redirected(), this, location302);
170 
171         coralizeState = CST_DEFAULT;
172         downloadFile(location302);
173 
174     } else if(aLine == "\x0d") {
175         socket->setDataMode(size);
176     } else if(Util::findSubString(aLine, "Content-Length") != string::npos) {
177         size = Util::toInt(aLine.substr(16, aLine.length() - 17));
178     } else if(Util::findSubString(aLine, "Content-Encoding") != string::npos) {
179         if(aLine.substr(18, aLine.length() - 19) == "x-bzip2")
180             fire(HttpConnectionListener::TypeBZ2(), this);
181     }
182 }
183 
on(BufferedSocketListener::Failed,const string & aLine)184 void HttpConnection::on(BufferedSocketListener::Failed, const string& aLine) noexcept {
185     socket->removeListener(this);
186     BufferedSocket::putSocket(socket);
187     socket = NULL;
188     if(SETTING(CORAL) && coralizeState != CST_NOCORALIZE) {
189         fire(HttpConnectionListener::Retried(), this, coralizeState == CST_CONNECTED);
190         coralizeState = CST_NOCORALIZE;
191         dcdebug("Coralized address failed, retrying : %s\n",currentUrl.c_str());
192         downloadFile(currentUrl);
193         return;
194     }
195     coralizeState = CST_DEFAULT;
196     fire(HttpConnectionListener::Failed(), this, aLine + " (" + currentUrl + ")");
197 }
198 
on(BufferedSocketListener::ModeChange)199 void HttpConnection::on(BufferedSocketListener::ModeChange) noexcept {
200     socket->removeListener(this);
201     socket->disconnect();
202     BufferedSocket::putSocket(socket);
203     socket = NULL;
204     fire(HttpConnectionListener::Complete(), this, currentUrl, BOOLSETTING(CORAL) && coralizeState != CST_NOCORALIZE);
205     coralizeState = CST_DEFAULT;
206 }
on(BufferedSocketListener::Data,uint8_t * aBuf,size_t aLen)207 void HttpConnection::on(BufferedSocketListener::Data, uint8_t* aBuf, size_t aLen) noexcept {
208     fire(HttpConnectionListener::Data(), this, aBuf, aLen);
209 }
210 
211 } // namespace dcpp
212