* [ABI break] merge support for http redirects, thanks to
[ntk/apt.git] / apt-pkg / acquire-method.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: acquire-method.cc,v 1.27.2.1 2003/12/24 23:09:17 mdz Exp $
4 /* ######################################################################
5
6 Acquire Method
7
8 This is a skeleton class that implements most of the functionality
9 of a method and some useful functions to make method implementation
10 simpler. The methods all derive this and specialize it. The most
11 complex implementation is the http method which needs to provide
12 pipelining, it runs the message engine at the same time it is
13 downloading files..
14
15 ##################################################################### */
16 /*}}}*/
17 // Include Files /*{{{*/
18 #include <apt-pkg/acquire-method.h>
19 #include <apt-pkg/error.h>
20 #include <apt-pkg/configuration.h>
21 #include <apt-pkg/strutl.h>
22 #include <apt-pkg/fileutl.h>
23 #include <apt-pkg/hashes.h>
24
25 #include <iostream>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <sys/signal.h>
30 /*}}}*/
31
32 using namespace std;
33
34 // AcqMethod::pkgAcqMethod - Constructor /*{{{*/
35 // ---------------------------------------------------------------------
36 /* This constructs the initialization text */
37 pkgAcqMethod::pkgAcqMethod(const char *Ver,unsigned long Flags)
38 {
39 char S[300] = "";
40 char *End = S;
41 strcat(End,"100 Capabilities\n");
42 sprintf(End+strlen(End),"Version: %s\n",Ver);
43
44 if ((Flags & SingleInstance) == SingleInstance)
45 strcat(End,"Single-Instance: true\n");
46
47 if ((Flags & Pipeline) == Pipeline)
48 strcat(End,"Pipeline: true\n");
49
50 if ((Flags & SendConfig) == SendConfig)
51 strcat(End,"Send-Config: true\n");
52
53 if ((Flags & LocalOnly) == LocalOnly)
54 strcat(End,"Local-Only: true\n");
55
56 if ((Flags & NeedsCleanup) == NeedsCleanup)
57 strcat(End,"Needs-Cleanup: true\n");
58
59 if ((Flags & Removable) == Removable)
60 strcat(End,"Removable: true\n");
61 strcat(End,"\n");
62
63 if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
64 exit(100);
65
66 SetNonBlock(STDIN_FILENO,true);
67
68 Queue = 0;
69 QueueBack = 0;
70 }
71 /*}}}*/
72 // AcqMethod::Fail - A fetch has failed /*{{{*/
73 // ---------------------------------------------------------------------
74 /* */
75 void pkgAcqMethod::Fail(bool Transient)
76 {
77 string Err = "Undetermined Error";
78 if (_error->empty() == false)
79 _error->PopMessage(Err);
80 _error->Discard();
81 Fail(Err,Transient);
82 }
83 /*}}}*/
84 // AcqMethod::Fail - A fetch has failed /*{{{*/
85 // ---------------------------------------------------------------------
86 /* */
87 void pkgAcqMethod::Fail(string Err,bool Transient)
88 {
89 // Strip out junk from the error messages
90 for (string::iterator I = Err.begin(); I != Err.end(); I++)
91 {
92 if (*I == '\r')
93 *I = ' ';
94 if (*I == '\n')
95 *I = ' ';
96 }
97
98 char S[1024];
99 char *End = S;
100 if (Queue != 0)
101 {
102 End += snprintf(S,sizeof(S)-50,"400 URI Failure\nURI: %s\n"
103 "Message: %s %s\n",Queue->Uri.c_str(), Err.c_str(), IP.c_str());
104 // Dequeue
105 FetchItem *Tmp = Queue;
106 Queue = Queue->Next;
107 delete Tmp;
108 if (Tmp == QueueBack)
109 QueueBack = Queue;
110 }
111 else
112 {
113 End += snprintf(S,sizeof(S)-50,"400 URI Failure\nURI: <UNKNOWN>\n"
114 "Message: %s\n",Err.c_str());
115 }
116 if(FailReason.empty() == false)
117 End += snprintf(End,sizeof(S)-50 - (End - S),"FailReason: %s\n",FailReason.c_str());
118 if (UsedMirror.empty() == false)
119 End += snprintf(End,sizeof(S)-50 - (End - S),"UsedMirror: %s\n",UsedMirror.c_str());
120 // Set the transient flag
121 if (Transient == true)
122 strcat(S,"Transient-Failure: true\n\n");
123 else
124 strcat(S,"\n");
125
126 if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
127 exit(100);
128 }
129 /*}}}*/
130 // AcqMethod::URIStart - Indicate a download is starting /*{{{*/
131 // ---------------------------------------------------------------------
132 /* */
133 void pkgAcqMethod::URIStart(FetchResult &Res)
134 {
135 if (Queue == 0)
136 abort();
137
138 char S[1024] = "";
139 char *End = S;
140
141 End += snprintf(S,sizeof(S),"200 URI Start\nURI: %s\n",Queue->Uri.c_str());
142 if (Res.Size != 0)
143 End += snprintf(End,sizeof(S)-4 - (End - S),"Size: %lu\n",Res.Size);
144
145 if (Res.LastModified != 0)
146 End += snprintf(End,sizeof(S)-4 - (End - S),"Last-Modified: %s\n",
147 TimeRFC1123(Res.LastModified).c_str());
148
149 if (Res.ResumePoint != 0)
150 End += snprintf(End,sizeof(S)-4 - (End - S),"Resume-Point: %lu\n",
151 Res.ResumePoint);
152
153 strcat(End,"\n");
154 if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
155 exit(100);
156 }
157 /*}}}*/
158 // AcqMethod::URIDone - A URI is finished /*{{{*/
159 // ---------------------------------------------------------------------
160 /* */
161 void pkgAcqMethod::URIDone(FetchResult &Res, FetchResult *Alt)
162 {
163 if (Queue == 0)
164 abort();
165
166 char S[1024] = "";
167 char *End = S;
168
169 End += snprintf(S,sizeof(S),"201 URI Done\nURI: %s\n",Queue->Uri.c_str());
170
171 if (Res.Filename.empty() == false)
172 End += snprintf(End,sizeof(S)-50 - (End - S),"Filename: %s\n",Res.Filename.c_str());
173
174 if (Res.Size != 0)
175 End += snprintf(End,sizeof(S)-50 - (End - S),"Size: %lu\n",Res.Size);
176
177 if (Res.LastModified != 0)
178 End += snprintf(End,sizeof(S)-50 - (End - S),"Last-Modified: %s\n",
179 TimeRFC1123(Res.LastModified).c_str());
180
181 if (Res.MD5Sum.empty() == false)
182 {
183 End += snprintf(End,sizeof(S)-50 - (End - S),"MD5-Hash: %s\n",Res.MD5Sum.c_str());
184 End += snprintf(End,sizeof(S)-50 - (End - S),"MD5Sum-Hash: %s\n",Res.MD5Sum.c_str());
185 }
186 if (Res.SHA1Sum.empty() == false)
187 End += snprintf(End,sizeof(S)-50 - (End - S),"SHA1-Hash: %s\n",Res.SHA1Sum.c_str());
188 if (Res.SHA256Sum.empty() == false)
189 End += snprintf(End,sizeof(S)-50 - (End - S),"SHA256-Hash: %s\n",Res.SHA256Sum.c_str());
190 if (UsedMirror.empty() == false)
191 End += snprintf(End,sizeof(S)-50 - (End - S),"UsedMirror: %s\n",UsedMirror.c_str());
192 if (Res.GPGVOutput.size() > 0)
193 End += snprintf(End,sizeof(S)-50 - (End - S),"GPGVOutput:\n");
194 for (vector<string>::iterator I = Res.GPGVOutput.begin();
195 I != Res.GPGVOutput.end(); I++)
196 End += snprintf(End,sizeof(S)-50 - (End - S), " %s\n", (*I).c_str());
197
198 if (Res.ResumePoint != 0)
199 End += snprintf(End,sizeof(S)-50 - (End - S),"Resume-Point: %lu\n",
200 Res.ResumePoint);
201
202 if (Res.IMSHit == true)
203 strcat(End,"IMS-Hit: true\n");
204 End = S + strlen(S);
205
206 if (Alt != 0)
207 {
208 if (Alt->Filename.empty() == false)
209 End += snprintf(End,sizeof(S)-50 - (End - S),"Alt-Filename: %s\n",Alt->Filename.c_str());
210
211 if (Alt->Size != 0)
212 End += snprintf(End,sizeof(S)-50 - (End - S),"Alt-Size: %lu\n",Alt->Size);
213
214 if (Alt->LastModified != 0)
215 End += snprintf(End,sizeof(S)-50 - (End - S),"Alt-Last-Modified: %s\n",
216 TimeRFC1123(Alt->LastModified).c_str());
217
218 if (Alt->MD5Sum.empty() == false)
219 End += snprintf(End,sizeof(S)-50 - (End - S),"Alt-MD5-Hash: %s\n",
220 Alt->MD5Sum.c_str());
221 if (Alt->SHA1Sum.empty() == false)
222 End += snprintf(End,sizeof(S)-50 - (End - S),"Alt-SHA1-Hash: %s\n",
223 Alt->SHA1Sum.c_str());
224 if (Alt->SHA256Sum.empty() == false)
225 End += snprintf(End,sizeof(S)-50 - (End - S),"Alt-SHA256-Hash: %s\n",
226 Alt->SHA256Sum.c_str());
227
228 if (Alt->IMSHit == true)
229 strcat(End,"Alt-IMS-Hit: true\n");
230 }
231
232 strcat(End,"\n");
233 if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
234 exit(100);
235
236 // Dequeue
237 FetchItem *Tmp = Queue;
238 Queue = Queue->Next;
239 delete Tmp;
240 if (Tmp == QueueBack)
241 QueueBack = Queue;
242 }
243 /*}}}*/
244 // AcqMethod::MediaFail - Syncronous request for new media /*{{{*/
245 // ---------------------------------------------------------------------
246 /* This sends a 403 Media Failure message to the APT and waits for it
247 to be ackd */
248 bool pkgAcqMethod::MediaFail(string Required,string Drive)
249 {
250 char S[1024];
251 snprintf(S,sizeof(S),"403 Media Failure\nMedia: %s\nDrive: %s\n\n",
252 Required.c_str(),Drive.c_str());
253
254 if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
255 exit(100);
256
257 vector<string> MyMessages;
258
259 /* Here we read messages until we find a 603, each non 603 message is
260 appended to the main message list for later processing */
261 while (1)
262 {
263 if (WaitFd(STDIN_FILENO) == false)
264 return false;
265
266 if (ReadMessages(STDIN_FILENO,MyMessages) == false)
267 return false;
268
269 string Message = MyMessages.front();
270 MyMessages.erase(MyMessages.begin());
271
272 // Fetch the message number
273 char *End;
274 int Number = strtol(Message.c_str(),&End,10);
275 if (End == Message.c_str())
276 {
277 cerr << "Malformed message!" << endl;
278 exit(100);
279 }
280
281 // Change ack
282 if (Number == 603)
283 {
284 while (MyMessages.empty() == false)
285 {
286 Messages.push_back(MyMessages.front());
287 MyMessages.erase(MyMessages.begin());
288 }
289
290 return !StringToBool(LookupTag(Message,"Failed"),false);
291 }
292
293 Messages.push_back(Message);
294 }
295 }
296 /*}}}*/
297 // AcqMethod::Configuration - Handle the configuration message /*{{{*/
298 // ---------------------------------------------------------------------
299 /* This parses each configuration entry and puts it into the _config
300 Configuration class. */
301 bool pkgAcqMethod::Configuration(string Message)
302 {
303 ::Configuration &Cnf = *_config;
304
305 const char *I = Message.c_str();
306 const char *MsgEnd = I + Message.length();
307
308 unsigned int Length = strlen("Config-Item");
309 for (; I + Length < MsgEnd; I++)
310 {
311 // Not a config item
312 if (I[Length] != ':' || stringcasecmp(I,I+Length,"Config-Item") != 0)
313 continue;
314
315 I += Length + 1;
316
317 for (; I < MsgEnd && *I == ' '; I++);
318 const char *Equals = I;
319 for (; Equals < MsgEnd && *Equals != '='; Equals++);
320 const char *End = Equals;
321 for (; End < MsgEnd && *End != '\n'; End++);
322 if (End == Equals)
323 return false;
324
325 Cnf.Set(DeQuoteString(string(I,Equals-I)),
326 DeQuoteString(string(Equals+1,End-Equals-1)));
327 I = End;
328 }
329
330 return true;
331 }
332 /*}}}*/
333 // AcqMethod::Run - Run the message engine /*{{{*/
334 // ---------------------------------------------------------------------
335 /* Fetch any messages and execute them. In single mode it returns 1 if
336 there are no more available messages - any other result is a
337 fatal failure code! */
338 int pkgAcqMethod::Run(bool Single)
339 {
340 while (1)
341 {
342 // Block if the message queue is empty
343 if (Messages.empty() == true)
344 {
345 if (Single == false)
346 if (WaitFd(STDIN_FILENO) == false)
347 break;
348 if (ReadMessages(STDIN_FILENO,Messages) == false)
349 break;
350 }
351
352 // Single mode exits if the message queue is empty
353 if (Single == true && Messages.empty() == true)
354 return -1;
355
356 string Message = Messages.front();
357 Messages.erase(Messages.begin());
358
359 // Fetch the message number
360 char *End;
361 int Number = strtol(Message.c_str(),&End,10);
362 if (End == Message.c_str())
363 {
364 cerr << "Malformed message!" << endl;
365 return 100;
366 }
367
368 switch (Number)
369 {
370 case 601:
371 if (Configuration(Message) == false)
372 return 100;
373 break;
374
375 case 600:
376 {
377 FetchItem *Tmp = new FetchItem;
378
379 Tmp->Uri = LookupTag(Message,"URI");
380 Tmp->DestFile = LookupTag(Message,"FileName");
381 if (StrToTime(LookupTag(Message,"Last-Modified"),Tmp->LastModified) == false)
382 Tmp->LastModified = 0;
383 Tmp->IndexFile = StringToBool(LookupTag(Message,"Index-File"),false);
384 Tmp->Next = 0;
385
386 // Append it to the list
387 FetchItem **I = &Queue;
388 for (; *I != 0; I = &(*I)->Next);
389 *I = Tmp;
390 if (QueueBack == 0)
391 QueueBack = Tmp;
392
393 // Notify that this item is to be fetched.
394 if (Fetch(Tmp) == false)
395 Fail();
396
397 break;
398 }
399 }
400 }
401
402 Exit();
403 return 0;
404 }
405 /*}}}*/
406 // AcqMethod::Log - Send a log message /*{{{*/
407 // ---------------------------------------------------------------------
408 /* */
409 void pkgAcqMethod::Log(const char *Format,...)
410 {
411 string CurrentURI = "<UNKNOWN>";
412 if (Queue != 0)
413 CurrentURI = Queue->Uri;
414
415 va_list args;
416 va_start(args,Format);
417
418 // sprintf the description
419 char S[1024];
420 unsigned int Len = snprintf(S,sizeof(S)-4,"101 Log\nURI: %s\n"
421 "Message: ",CurrentURI.c_str());
422
423 vsnprintf(S+Len,sizeof(S)-4-Len,Format,args);
424 strcat(S,"\n\n");
425
426 if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
427 exit(100);
428 }
429 /*}}}*/
430 // AcqMethod::Status - Send a status message /*{{{*/
431 // ---------------------------------------------------------------------
432 /* */
433 void pkgAcqMethod::Status(const char *Format,...)
434 {
435 string CurrentURI = "<UNKNOWN>";
436 if (Queue != 0)
437 CurrentURI = Queue->Uri;
438
439 va_list args;
440 va_start(args,Format);
441
442 // sprintf the description
443 char S[1024];
444 unsigned int Len = snprintf(S,sizeof(S)-4,"102 Status\nURI: %s\n"
445 "Message: ",CurrentURI.c_str());
446
447 vsnprintf(S+Len,sizeof(S)-4-Len,Format,args);
448 strcat(S,"\n\n");
449
450 if (write(STDOUT_FILENO,S,strlen(S)) != (signed)strlen(S))
451 exit(100);
452 }
453 /*}}}*/
454
455 // AcqMethod::Redirect - Send a redirect message /*{{{*/
456 // ---------------------------------------------------------------------
457 /* This method sends the redirect message and also manipulates the queue
458 to keep the pipeline synchronized. */
459 void pkgAcqMethod::Redirect(const string &NewURI)
460 {
461 string CurrentURI = "<UNKNOWN>";
462 if (Queue != 0)
463 CurrentURI = Queue->Uri;
464
465 char S[1024];
466 snprintf(S, sizeof(S)-50, "103 Redirect\nURI: %s\nNew-URI: %s\n\n",
467 CurrentURI.c_str(), NewURI.c_str());
468
469 if (write(STDOUT_FILENO,S,strlen(S)) != (ssize_t)strlen(S))
470 exit(100);
471
472 // Change the URI for the request.
473 Queue->Uri = NewURI;
474
475 /* To keep the pipeline synchronized, move the current request to
476 the end of the queue, past the end of the current pipeline. */
477 FetchItem *I;
478 for (I = Queue; I->Next != 0; I = I->Next) ;
479 I->Next = Queue;
480 Queue = Queue->Next;
481 I->Next->Next = 0;
482 if (QueueBack == 0)
483 QueueBack = I->Next;
484 }
485 /*}}}*/
486
487 // AcqMethod::FetchResult::FetchResult - Constructor /*{{{*/
488 // ---------------------------------------------------------------------
489 /* */
490 pkgAcqMethod::FetchResult::FetchResult() : LastModified(0),
491 IMSHit(false), Size(0), ResumePoint(0)
492 {
493 }
494 /*}}}*/
495 // AcqMethod::FetchResult::TakeHashes - Load hashes /*{{{*/
496 // ---------------------------------------------------------------------
497 /* This hides the number of hashes we are supporting from the caller.
498 It just deals with the hash class. */
499 void pkgAcqMethod::FetchResult::TakeHashes(Hashes &Hash)
500 {
501 MD5Sum = Hash.MD5.Result();
502 SHA1Sum = Hash.SHA1.Result();
503 SHA256Sum = Hash.SHA256.Result();
504 }
505 /*}}}*/