HttpUtils.pm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. ##############################################
  2. # $Id: HttpUtils.pm 13084 2017-01-15 12:59:16Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use IO::Socket::INET;
  7. use MIME::Base64;
  8. use Digest::MD5 qw(md5_hex);
  9. use vars qw($SSL_ERROR);
  10. my %ext2MIMEType= qw{
  11. bmp image/bmp
  12. css text/css
  13. gif image/gif
  14. html text/html
  15. ico image/x-icon
  16. jpg image/jpeg
  17. js text/javascript
  18. pdf application/pdf
  19. png image/png
  20. svg image/svg+xml
  21. txt text/plain
  22. };
  23. my $HU_use_zlib;
  24. sub
  25. ext2MIMEType($) {
  26. my ($ext)= @_;
  27. return "text/plain" if(!$ext);
  28. my $MIMEType = $ext2MIMEType{lc($ext)};
  29. return ($MIMEType ? $MIMEType : "text/$ext");
  30. }
  31. sub
  32. filename2MIMEType($) {
  33. my ($filename)= @_;
  34. $filename =~ m/^.*\.([^\.]*)$/;
  35. return ext2MIMEType($1);
  36. }
  37. ##################
  38. sub
  39. urlEncode($) {
  40. $_= $_[0];
  41. s/([\x00-\x2F,\x3A-\x40,\x5B-\x60,\x7B-\xFF])/sprintf("%%%02x",ord($1))/eg;
  42. return $_;
  43. }
  44. ##################
  45. sub
  46. urlDecode($) {
  47. $_= $_[0];
  48. s/%([0-9A-F][0-9A-F])/chr(hex($1))/egi;
  49. return $_;
  50. }
  51. sub
  52. HttpUtils_Close($)
  53. {
  54. my ($hash) = @_;
  55. delete($hash->{FD});
  56. delete($selectlist{$hash});
  57. $hash->{conn}->close() if(defined($hash->{conn}));
  58. delete($hash->{conn});
  59. delete($hash->{hu_sslAdded});
  60. delete($hash->{hu_filecount});
  61. delete($hash->{hu_blocking});
  62. delete($hash->{hu_portSfx});
  63. delete($hash->{directReadFn});
  64. delete($hash->{directWriteFn});
  65. }
  66. sub
  67. HttpUtils_Err($)
  68. {
  69. my ($lhash, $errtxt) = @_;
  70. my $hash = $lhash->{hash};
  71. if($lhash->{sts} && $lhash->{sts} == $selectTimestamp) { # busy loop check
  72. Log 4, "extending '$lhash->{msg} $hash->{addr}' timeout due to busy loop";
  73. InternalTimer(gettimeofday()+1, "HttpUtils_Err", $lhash);
  74. return;
  75. }
  76. return if(!defined($hash->{FD})); # Already closed
  77. HttpUtils_Close($hash);
  78. $hash->{callback}($hash, "$lhash->{msg} $hash->{addr} timed out", "");
  79. }
  80. sub
  81. HttpUtils_File($)
  82. {
  83. my ($hash) = @_;
  84. return 0 if($hash->{url} !~ m+^file://(.*)$+);
  85. my $fName = $1;
  86. return (1, "Absolute URL is not supported") if($fName =~ m+^/+);
  87. return (1, ".. in URL is not supported") if($fName =~ m+\.\.+);
  88. open(FH, $fName) || return(1, "$fName: $!");
  89. my $data = join("", <FH>);
  90. close(FH);
  91. return (1, undef, $data);
  92. }
  93. sub ip2str($) { return sprintf("%d.%d.%d.%d", unpack("C*", shift)); }
  94. # http://www.ccs.neu.edu/home/amislove/teaching/cs4700/fall09/handouts/project1-primer.pdf
  95. my %HU_dnsCache;
  96. sub
  97. HttpUtils_dnsParse($$)
  98. {
  99. my ($a, $ql) = @_; # $ql: avoid hardcoding query length
  100. return "wrong message ID" if(unpack("H*",substr($a,0,2)) ne "7072");
  101. while(length($a) >= $ql+16) {
  102. my $l = unpack("C",substr($a,$ql, 1));
  103. if(($l & 0xC0) == 0xC0) { # DNS packed compression
  104. $ql += 2;
  105. } else {
  106. while($l != 0) {
  107. $ql += $l+1;
  108. $l = unpack("C",substr($a,$ql,2));
  109. }
  110. $ql++;
  111. }
  112. return (undef, substr($a,$ql+10,4), unpack("N",substr($a,$ql+4,4)))
  113. if(unpack("N",substr($a,$ql,4)) == 0x10001);
  114. $ql += 10+unpack("n",substr($a,$ql+8)) if(length($a) >= $ql+10);
  115. }
  116. return "No A record found";
  117. }
  118. # { HttpUtils_gethostbyname({timeout=>4}, "google.com", sub(){my($h,$e,$a)=@_;;
  119. # fhem("trigger global ".($e ? "ERR:$e": ("IP:".ip2str($a)))) }) }
  120. sub
  121. HttpUtils_gethostbyname($$$)
  122. {
  123. my ($hash, $host, $fn) = @_;
  124. if($host =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ && # IP-Address
  125. $1<256 && $2<256 && $3<256 && $4<256) {
  126. $fn->($hash, undef, pack("CCCC", $1, $2, $3, $4));
  127. return;
  128. }
  129. my $dnsServer = AttrVal("global", "dnsServer", undef);
  130. if(!$dnsServer) {
  131. my $iaddr = inet_aton($host);
  132. my $err;
  133. if(!defined($iaddr)) {
  134. $iaddr = gethostbyname($host); # This is still blocking
  135. $err = (($iaddr && length($iaddr)==4) ?
  136. undef : "gethostbyname $host failed");
  137. }
  138. $fn->($hash, $err, $iaddr);
  139. return;
  140. }
  141. return $fn->($hash, undef, $HU_dnsCache{$host}{addr}) # check the cache
  142. if($HU_dnsCache{$host} &&
  143. $HU_dnsCache{$host}{TS}+$HU_dnsCache{$host}{TTL} > gettimeofday());
  144. # Direct DNS Query via UDP
  145. my $c = IO::Socket::INET->new(Proto=>'udp', PeerAddr=>"$dnsServer:53");
  146. return $fn->($hash, "Cant create UDP socket:$!", undef) if(!$c);
  147. my %dh = ( conn=>$c, FD=>$c->fileno(), NAME=>"DNS", origHash=>$hash,
  148. addr=>$dnsServer, callback=>$fn );
  149. my %timerHash = ( hash=>\%dh, msg=>"DNS" );
  150. my $bhost = join("", map { pack("CA*",length($_),$_) } split(/\./, $host));
  151. my $qry = pack("nnnnnn", 0x7072,0x0100,1,0,0,0) . $bhost . pack("Cnn", 0,1,1);
  152. my $ql = length($qry);
  153. $dh{directReadFn} = sub() { # Parse the answer
  154. RemoveInternalTimer(\%timerHash);
  155. my $buf;
  156. my $len = sysread($dh{conn},$buf,65536);
  157. HttpUtils_Close(\%dh);
  158. Log 5, "DNS ANSWER ".($len?$len:0).":".($buf ? unpack("H*", $buf):"N/A");
  159. my ($err, $addr, $ttl) = HttpUtils_dnsParse($buf,$ql);
  160. return $fn->($hash, "DNS: $err", undef) if($err);
  161. Log 4, "DNS result for $host: ".ip2str($addr).", ttl:$ttl";
  162. $HU_dnsCache{$host}{TS} = gettimeofday();
  163. $HU_dnsCache{$host}{TTL} = $ttl;
  164. $HU_dnsCache{$host}{addr} = $addr;
  165. return $fn->($hash, undef, $addr);
  166. };
  167. $selectlist{\%dh} = \%dh;
  168. my $dnsQuery;
  169. my $dnsTo = 0.25;
  170. my $lSelectTs = $selectTimestamp;
  171. $dnsQuery = sub()
  172. {
  173. $dnsTo *= 2 if($lSelectTs != $selectTimestamp);
  174. $lSelectTs = $selectTimestamp;
  175. return HttpUtils_Err(\%timerHash) if($dnsTo > $hash->{timeout}/2);
  176. my $ret = syswrite $dh{conn}, $qry;
  177. if(!$ret || $ret != $ql) {
  178. my $err = $!;
  179. HttpUtils_Close(\%dh);
  180. return $fn->($hash, "DNS write error: $err", undef);
  181. }
  182. InternalTimer(gettimeofday()+$dnsTo, $dnsQuery, \%timerHash);
  183. };
  184. $dnsQuery->();
  185. }
  186. sub
  187. HttpUtils_Connect($)
  188. {
  189. my ($hash) = @_;
  190. $hash->{timeout} = 4 if(!defined($hash->{timeout}));
  191. $hash->{loglevel} = 4 if(!$hash->{loglevel});
  192. $hash->{redirects} = 0 if(!$hash->{redirects});
  193. $hash->{displayurl} = $hash->{hideurl} ? "<hidden>" : $hash->{url};
  194. $hash->{sslargs} = {} if(!defined($hash->{sslargs}));
  195. Log3 $hash, $hash->{loglevel}, "HttpUtils url=$hash->{displayurl}";
  196. if($hash->{url} !~
  197. /^(http|https):\/\/(([^:\/]+):([^:\/]+)@)?([^:\/]+)(:\d+)?(\/.*)$/) {
  198. return "$hash->{displayurl}: malformed or unsupported URL";
  199. }
  200. my ($authstring,$user,$pwd,$port,$host);
  201. ($hash->{protocol},$authstring,$user,$pwd,$host,$port,$hash->{path})
  202. = ($1,$2,$3,$4,$5,$6,$7);
  203. $hash->{host} = $host;
  204. if(defined($port)) {
  205. $port =~ s/^://;
  206. } else {
  207. $port = ($hash->{protocol} eq "https" ? 443: 80);
  208. }
  209. $hash->{hu_portSfx} = ($port =~ m/^(80|443)$/ ? "" : ":$port");
  210. $hash->{path} = '/' unless defined($hash->{path});
  211. $hash->{addr} = "$hash->{protocol}://$host:$port";
  212. $hash->{auth} = "$user:$pwd" if($authstring);
  213. return HttpUtils_Connect2($hash) if($hash->{conn} && $hash->{keepalive});
  214. if($hash->{callback}) { # Nonblocking staff
  215. $hash->{conn} = IO::Socket::INET->new(Proto=>'tcp', Blocking=>0);
  216. if($hash->{conn}) {
  217. HttpUtils_gethostbyname($hash, $host, sub($$$) {
  218. my ($hash, $err, $iaddr) = @_;
  219. $hash = $hash->{origHash} if($hash->{origHash});
  220. if($err) {
  221. HttpUtils_Close($hash);
  222. return $hash->{callback}($hash, $err, "") ;
  223. }
  224. return $hash->{callback}($hash, "Closed conn / parallel call?", "")
  225. if(!$hash->{conn});
  226. my $ret = connect($hash->{conn}, sockaddr_in($port, $iaddr));
  227. if(!$ret) {
  228. if($!{EINPROGRESS} || int($!)==10035 ||
  229. (int($!)==140 && $^O eq "MSWin32")) { # Nonblocking connect
  230. $hash->{FD} = $hash->{conn}->fileno();
  231. my %timerHash=(hash=>$hash,sts=>$selectTimestamp,msg=>"connect to");
  232. $hash->{directWriteFn} = sub() {
  233. delete($hash->{FD});
  234. delete($hash->{directWriteFn});
  235. delete($selectlist{$hash});
  236. RemoveInternalTimer(\%timerHash);
  237. my $packed = getsockopt($hash->{conn}, SOL_SOCKET, SO_ERROR);
  238. my $errno = unpack("I",$packed);
  239. if($errno) {
  240. HttpUtils_Close($hash);
  241. return $hash->{callback}($hash, "$host: ".strerror($errno), "");
  242. }
  243. my $err = HttpUtils_Connect2($hash);
  244. $hash->{callback}($hash, $err, "") if($err);
  245. return $err;
  246. };
  247. $hash->{NAME}="" if(!defined($hash->{NAME}));#Delete might check it
  248. $selectlist{$hash} = $hash;
  249. InternalTimer(gettimeofday()+$hash->{timeout},
  250. "HttpUtils_Err", \%timerHash);
  251. return undef;
  252. } else {
  253. HttpUtils_Close($hash);
  254. $hash->{callback}($hash, "connect to $hash->{addr}: $!", "");
  255. return undef;
  256. }
  257. }
  258. });
  259. return;
  260. }
  261. } else {
  262. $hash->{conn} = IO::Socket::INET->new(
  263. PeerAddr=>"$host:$port", Timeout=>$hash->{timeout});
  264. return "$hash->{displayurl}: Can't connect(1) to $hash->{addr}: $@"
  265. if(!$hash->{conn});
  266. }
  267. if($hash->{compress}) {
  268. if(!defined($HU_use_zlib)) {
  269. $HU_use_zlib = 1;
  270. eval { require Compress::Zlib; };
  271. $HU_use_zlib = 0 if($@);
  272. }
  273. $hash->{compress} = $HU_use_zlib;
  274. }
  275. return HttpUtils_Connect2($hash);
  276. }
  277. sub
  278. HttpUtils_Connect2($)
  279. {
  280. my ($hash) = @_;
  281. $hash->{host} =~ s/:.*//;
  282. if($hash->{protocol} eq "https" && $hash->{conn} && !$hash->{hu_sslAdded}) {
  283. eval "use IO::Socket::SSL";
  284. if($@) {
  285. Log3 $hash, $hash->{loglevel}, $@;
  286. } else {
  287. $hash->{conn}->blocking(1);
  288. my $sslVersion = AttrVal($hash->{NAME}, "sslVersion",
  289. AttrVal("global", "sslVersion", "SSLv23:!SSLv3:!SSLv2"));
  290. my %par = %{$hash->{sslargs}};
  291. $par{Timeout} = $hash->{timeout};
  292. $par{SSL_version} = $sslVersion;
  293. $par{SSL_hostname} = $hash->{host}
  294. if(IO::Socket::SSL->can('can_client_sni') &&
  295. IO::Socket::SSL->can_client_sni());
  296. IO::Socket::SSL->start_SSL($hash->{conn}, \%par) || undef $hash->{conn};
  297. $hash->{hu_sslAdded} = 1 if($hash->{keepalive});
  298. }
  299. }
  300. if(!$hash->{conn}) {
  301. undef $hash->{conn};
  302. my $err = $@;
  303. if($hash->{protocol} eq "https") {
  304. $err = "" if(!$err);
  305. $err .= " ".($SSL_ERROR ? $SSL_ERROR : IO::Socket::SSL::errstr());
  306. }
  307. return "$hash->{displayurl}: Can't connect(2) to $hash->{addr}: $err";
  308. }
  309. if($hash->{noConn2}) {
  310. $hash->{callback}($hash);
  311. return undef;
  312. }
  313. my $data;
  314. if(defined($hash->{data})) {
  315. if( ref($hash->{data}) eq 'HASH' ) {
  316. foreach my $key (keys %{$hash->{data}}) {
  317. $data .= "&" if( $data );
  318. $data .= "$key=". urlEncode($hash->{data}{$key});
  319. }
  320. } else {
  321. $data = $hash->{data};
  322. }
  323. }
  324. if(defined($hash->{header})) {
  325. if( ref($hash->{header}) eq 'HASH' ) {
  326. $hash->{header} = join("\r\n",
  327. map(($_.': '.$hash->{header}{$_}), keys %{$hash->{header}}));
  328. }
  329. }
  330. my $method = $hash->{method};
  331. $method = ($data ? "POST" : "GET") if( !$method );
  332. my $httpVersion = $hash->{httpversion} ? $hash->{httpversion} : "1.0";
  333. my $hdr = "$method $hash->{path} HTTP/$httpVersion\r\n";
  334. $hdr .= "Host: $hash->{host}$hash->{hu_portSfx}\r\n";
  335. $hdr .= "User-Agent: fhem\r\n"
  336. if(!$hash->{header} || $hash->{header} !~ "User-Agent:");
  337. $hdr .= "Accept-Encoding: gzip,deflate\r\n" if($hash->{compress});
  338. $hdr .= "Connection: keep-alive\r\n" if($hash->{keepalive});
  339. $hdr .= "Connection: Close\r\n"
  340. if($httpVersion ne "1.0" && !$hash->{keepalive});
  341. $hdr .= "Authorization: Basic ".encode_base64($hash->{auth}, "")."\r\n"
  342. if(defined($hash->{auth}) &&
  343. !($hash->{header} &&
  344. $hash->{header} =~ /^Authorization:\s*Digest/mi));
  345. $hdr .= $hash->{header}."\r\n" if($hash->{header});
  346. if(defined($data)) {
  347. $hdr .= "Content-Length: ".length($data)."\r\n";
  348. $hdr .= "Content-Type: application/x-www-form-urlencoded\r\n"
  349. if ($hdr !~ "Content-Type:");
  350. }
  351. $hdr .= "\r\n";
  352. my $s = $hash->{shutdown};
  353. $s =(defined($hash->{noshutdown}) && $hash->{noshutdown}==0) if(!defined($s));
  354. $s = 0 if($hash->{protocol} eq "https");
  355. if($hash->{callback}) { # Nonblocking read
  356. $hash->{FD} = $hash->{conn}->fileno();
  357. $hash->{buf} = "";
  358. delete($hash->{httpdatalen});
  359. delete($hash->{httpheader});
  360. $hash->{NAME} = "" if(!defined($hash->{NAME}));
  361. my %timerHash = (hash=>$hash, checkSTS=>$selectTimestamp, msg=>"write to");
  362. $hash->{directReadFn} = sub() {
  363. my $buf;
  364. my $len = sysread($hash->{conn},$buf,65536);
  365. $hash->{buf} .= $buf if(defined($len) && $len > 0);
  366. if(!defined($len) || $len <= 0 ||
  367. HttpUtils_DataComplete($hash)) {
  368. delete($hash->{FD});
  369. delete($hash->{directReadFn});
  370. delete($selectlist{$hash});
  371. RemoveInternalTimer(\%timerHash);
  372. my ($err, $ret, $redirect) = HttpUtils_ParseAnswer($hash);
  373. $hash->{callback}($hash, $err, $ret) if(!$redirect);
  374. }
  375. };
  376. $data = $hdr.(defined($data) ? $data:"");
  377. $hash->{directWriteFn} = sub($) { # Nonblocking write
  378. my $ret = syswrite $hash->{conn}, $data;
  379. if($ret <= 0) {
  380. my $err = $!;
  381. RemoveInternalTimer(\%timerHash);
  382. HttpUtils_Close($hash);
  383. return $hash->{callback}($hash, "write error: $err", undef)
  384. }
  385. $data = substr($data,$ret);
  386. if(length($data) == 0) {
  387. shutdown($hash->{conn}, 1) if($s);
  388. delete($hash->{directWriteFn});
  389. RemoveInternalTimer(\%timerHash);
  390. $timerHash{msg} = "read from";
  391. InternalTimer(gettimeofday()+$hash->{timeout},
  392. "HttpUtils_Err", \%timerHash);
  393. }
  394. };
  395. $selectlist{$hash} = $hash;
  396. InternalTimer(gettimeofday()+$hash->{timeout}, "HttpUtils_Err",\%timerHash);
  397. return undef;
  398. } else {
  399. syswrite $hash->{conn}, $hdr;
  400. syswrite $hash->{conn}, $data if(defined($data));
  401. shutdown($hash->{conn}, 1) if($s);
  402. }
  403. return undef;
  404. }
  405. sub
  406. HttpUtils_DataComplete($)
  407. {
  408. my ($hash) = @_;
  409. my ($hdr, $data) = ($1, $2);
  410. my $hl = $hash->{httpdatalen};
  411. if(!defined($hl)) {
  412. return 0 if($hash->{buf} !~ m/^(.*?)\r?\n\r?\n(.*)$/s);
  413. my ($hdr, $data) = ($1, $2);
  414. if($hdr =~ m/Transfer-Encoding:\s*chunked/si) {
  415. $hash->{httpheader} = $hdr;
  416. $hash->{httpdata} = "";
  417. $hash->{buf} = $data;
  418. $hash->{httpdatalen} = -1;
  419. } elsif($hdr =~ m/Content-Length:\s*(\d+)/si) {
  420. $hash->{httpdatalen} = $1;
  421. $hash->{httpheader} = $hdr;
  422. $hash->{httpdata} = $data;
  423. $hash->{buf} = "";
  424. } else {
  425. $hash->{httpdatalen} = -2;
  426. }
  427. $hl = $hash->{httpdatalen};
  428. }
  429. return 0 if($hl == -2);
  430. if($hl == -1) { # chunked
  431. while($hash->{buf} =~ m/^[\r\n]*([0-9A-F]+)\r?\n(.*)$/si) {
  432. my ($l, $r) = (hex($1), $2);
  433. if($l == 0) {
  434. $hash->{buf} = "";
  435. return 1;
  436. }
  437. return 0 if(length($r) < $l);
  438. $hash->{httpdata} .= substr($r, 0, $l);
  439. $hash->{buf} = substr($r, $l);
  440. }
  441. return 0;
  442. } else {
  443. $hash->{httpdata} .= $hash->{buf};
  444. $hash->{buf} = "";
  445. return 0 if(length($hash->{httpdata}) < $hash->{httpdatalen});
  446. return 1;
  447. }
  448. }
  449. sub
  450. HttpUtils_DigestHeader($$)
  451. {
  452. my ($hash, $header) = @_;
  453. my %digdata;
  454. while($header =~ /(\w+)="?([^"]+?)"?(?:,\s+|$)/gc) {
  455. $digdata{$1} = $2;
  456. }
  457. my ($ha1, $ha2, $response);
  458. my ($user,$passwd) = split(/:/, $hash->{auth}, 2);
  459. if(exists($digdata{qop})) {
  460. $digdata{nc} = "00000001";
  461. $digdata{cnonce} = md5_hex(rand().time());
  462. }
  463. $digdata{uri} = $hash->{path};
  464. $digdata{username} = $user;
  465. if(exists($digdata{algorithm}) && $digdata{algorithm} eq "MD5-sess") {
  466. $ha1 = md5_hex(md5_hex($user.":".$digdata{realm}.":".$passwd).
  467. ":".$digdata{nonce}.":".$digdata{cnonce});
  468. } else {
  469. $ha1 = md5_hex($user.":".$digdata{realm}.":".$passwd);
  470. }
  471. # forcing qop=auth as qop=auth-int is not implemented
  472. $digdata{qop} = "auth" if($digdata{qop});
  473. my $method = $hash->{method};
  474. $method = ($hash->{data} ? "POST" : "GET") if( !$method );
  475. $ha2 = md5_hex($method.":".$hash->{path});
  476. if(exists($digdata{qop}) && $digdata{qop} =~ /(auth-int|auth)/) {
  477. $digdata{response} = md5_hex($ha1.":".
  478. $digdata{nonce}.":".
  479. $digdata{nc}.":".
  480. $digdata{cnonce}.":".
  481. $digdata{qop}.":".
  482. $ha2);
  483. } else {
  484. $digdata{response} = md5_hex($ha1.":".$digdata{nonce}.":".$ha2)
  485. }
  486. return "Authorization: Digest ".
  487. join(", ", map(($_.'='.($_ ne "nc" ? '"' :'').
  488. $digdata{$_}.($_ ne "nc" ? '"' :'')), keys(%digdata)));
  489. }
  490. sub
  491. HttpUtils_ParseAnswer($)
  492. {
  493. my ($hash) = @_;
  494. if(!$hash->{keepalive}) {
  495. $hash->{conn}->close();
  496. undef $hash->{conn};
  497. }
  498. if(!$hash->{buf} && !$hash->{httpheader}) {
  499. # Server answer: Keep-Alive: timeout=2, max=200
  500. if($hash->{keepalive} && $hash->{hu_filecount}) {
  501. my $bc = $hash->{hu_blocking};
  502. HttpUtils_Close($hash);
  503. if($bc) {
  504. return HttpUtils_BlockingGet($hash);
  505. } else {
  506. return HttpUtils_NonblockingGet($hash);
  507. }
  508. }
  509. return ("$hash->{displayurl}: empty answer received", "");
  510. }
  511. $hash->{hu_filecount} = 0 if(!$hash->{hu_filecount});
  512. $hash->{hu_filecount}++;
  513. if(!defined($hash->{httpheader})) { # response without Content-Length
  514. if($hash->{buf} =~ m/^(HTTP.*?)\r?\n\r?\n(.*)$/s) {
  515. $hash->{httpheader} = $1;
  516. $hash->{httpdata} = $2;
  517. } else {
  518. return ("", $hash->{buf});
  519. }
  520. }
  521. my $ret = $hash->{httpdata};
  522. $ret = "" if(!defined($ret));
  523. delete $hash->{httpdata};
  524. delete $hash->{httpdatalen};
  525. my @header= split("\r\n", $hash->{httpheader});
  526. my @header0= split(" ", shift @header);
  527. my $code= $header0[1];
  528. # Close if server doesn't support keepalive
  529. HttpUtils_Close($hash)
  530. if($hash->{keepalive} &&
  531. $hash->{httpheader} =~ m/^Connection:\s*close\s*$/mi);
  532. if(!defined($code) || $code eq "") {
  533. return ("$hash->{displayurl}: empty answer received", "");
  534. }
  535. Log3 $hash,$hash->{loglevel}, "$hash->{displayurl}: HTTP response code $code";
  536. $hash->{code} = $code;
  537. # if servers requests digest authentication
  538. if($code==401 && defined($hash->{auth}) &&
  539. !($hash->{header} && $hash->{header} =~ /^Authorization:\s*Digest/mi) &&
  540. $hash->{httpheader} =~ /^WWW-Authenticate:\s*Digest\s*(.+?)\s*$/mi) {
  541. $hash->{header} .= "\r\n".
  542. HttpUtils_DigestHeader($hash, $1) if($hash->{header});
  543. $hash->{header} = HttpUtils_DigestHeader($hash, $1) if(!$hash->{header});
  544. # Request the URL with the Digest response
  545. if($hash->{callback}) {
  546. HttpUtils_NonblockingGet($hash);
  547. return ("", "", 1);
  548. } else {
  549. return HttpUtils_BlockingGet($hash);
  550. }
  551. } elsif($code==401 && defined($hash->{auth})) {
  552. return ("$hash->{displayurl}: wrong authentication", "")
  553. }
  554. if(($code==301 || $code==302 || $code==303)
  555. && !$hash->{ignoreredirects}) { # redirect
  556. if(++$hash->{redirects} > 5) {
  557. return ("$hash->{displayurl}: Too many redirects", "");
  558. } else {
  559. my $ra;
  560. map { $ra=$1 if($_ =~ m/Location:\s*(\S+)$/) } @header;
  561. $ra = "/$ra" if($ra !~ m/^http/ && $ra !~ m/^\//);
  562. $hash->{url} = ($ra =~ m/^http/) ? $ra: $hash->{addr}.$ra;
  563. Log3 $hash, $hash->{loglevel}, "HttpUtils $hash->{displayurl}: ".
  564. "Redirect to ".($hash->{hideurl} ? "<hidden>" : $hash->{url});
  565. if($hash->{callback}) {
  566. HttpUtils_NonblockingGet($hash);
  567. return ("", "", 1);
  568. } else {
  569. return HttpUtils_BlockingGet($hash);
  570. }
  571. }
  572. }
  573. # Debug
  574. Log3 $hash, $hash->{loglevel},
  575. "HttpUtils $hash->{displayurl}: Got data, length: ". length($ret);
  576. if(!length($ret)) {
  577. Log3 $hash, $hash->{loglevel}, "HttpUtils $hash->{displayurl}: ".
  578. "Zero length data, header follows:";
  579. for (@header) {
  580. Log3 $hash, $hash->{loglevel}, " $_";
  581. }
  582. }
  583. return ("", $ret);
  584. }
  585. # Parameters in the hash:
  586. # mandatory:
  587. # url, callback
  588. # optional(default):
  589. # hideurl(0),timeout(4),data(""),loglevel(4),header("" or HASH),
  590. # noshutdown(1),shutdown(0),httpversion("1.0"),ignoreredirects(0)
  591. # method($data ? "POST" : "GET"),keepalive(0),sslargs({})
  592. # Example:
  593. # HttpUtils_NonblockingGet({ url=>"http://www.google.de/", myParam=>7,
  594. # callback=>sub($$$){ Log 1,"$_[0]->{myParam} ERR:$_[1] DATA:$_[2]" } })
  595. sub
  596. HttpUtils_NonblockingGet($)
  597. {
  598. my ($hash) = @_;
  599. $hash->{hu_blocking} = 0;
  600. my ($isFile, $fErr, $fContent) = HttpUtils_File($hash);
  601. return $hash->{callback}($hash, $fErr, $fContent) if($isFile);
  602. my $err = HttpUtils_Connect($hash);
  603. $hash->{callback}($hash, $err, "") if($err);
  604. }
  605. #################
  606. # Parameters same as HttpUtils_NonblockingGet up to callback
  607. # Returns (err,data)
  608. sub
  609. HttpUtils_BlockingGet($)
  610. {
  611. my ($hash) = @_;
  612. $hash->{hu_blocking} = 1;
  613. my ($isFile, $fErr, $fContent) = HttpUtils_File($hash);
  614. return ($fErr, $fContent) if($isFile);
  615. my $err = HttpUtils_Connect($hash);
  616. return ($err, undef) if($err);
  617. my $buf = "";
  618. $hash->{conn}->timeout($hash->{timeout});
  619. $hash->{buf} = "";
  620. delete($hash->{httpdatalen});
  621. delete($hash->{httpheader});
  622. for(;;) {
  623. my ($rout, $rin) = ('', '');
  624. vec($rin, $hash->{conn}->fileno(), 1) = 1;
  625. my $nfound = select($rout=$rin, undef, undef, $hash->{timeout});
  626. if($nfound <= 0) {
  627. undef $hash->{conn};
  628. return ("$hash->{displayurl}: Select timeout/error: $!", undef);
  629. }
  630. my $len = sysread($hash->{conn},$buf,65536);
  631. last if(!defined($len) || $len <= 0);
  632. $hash->{buf} .= $buf;
  633. last if(HttpUtils_DataComplete($hash));
  634. }
  635. return HttpUtils_ParseAnswer($hash);
  636. }
  637. # Deprecated, use GetFileFromURL/GetFileFromURLQuiet
  638. sub
  639. CustomGetFileFromURL($$@)
  640. {
  641. my ($hideurl, $url, $timeout, $data, $noshutdown, $loglevel) = @_;
  642. $loglevel = 4 if(!defined($loglevel));
  643. my $hash = { hideurl => $hideurl,
  644. url => $url,
  645. timeout => $timeout,
  646. data => $data,
  647. noshutdown=> $noshutdown,
  648. loglevel => $loglevel,
  649. };
  650. my ($err, $ret) = HttpUtils_BlockingGet($hash);
  651. if($err) {
  652. Log3 undef, $hash->{loglevel}, "CustomGetFileFromURL $err";
  653. return undef;
  654. }
  655. return $ret;
  656. }
  657. ##################
  658. # Parameter: $url, $timeout, $data, $noshutdown, $loglevel
  659. # - if data (which is urlEncoded) is set, then a POST is performed, else a GET.
  660. # - noshutdown must be set e.g. if calling the Fritz!Box Webserver
  661. sub
  662. GetFileFromURL($@)
  663. {
  664. my ($url, @a)= @_;
  665. return CustomGetFileFromURL(0, $url, @a);
  666. }
  667. ##################
  668. # Same as GetFileFromURL, but the url is not displayed in the log.
  669. sub
  670. GetFileFromURLQuiet($@)
  671. {
  672. my ($url, @a)= @_;
  673. return CustomGetFileFromURL(1, $url, @a);
  674. }
  675. sub
  676. GetHttpFile($$)
  677. {
  678. my ($host,$file) = @_;
  679. return GetFileFromURL("http://$host$file");
  680. }
  681. 1;