GithubHelp home page GithubHelp logo

laminas / laminas-http Goto Github PK

View Code? Open in Web Editor NEW
35.0 35.0 27.0 4.72 MB

Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests

Home Page: https://docs.laminas.dev/laminas-http/

License: BSD 3-Clause "New" or "Revised" License

PHP 99.79% Hack 0.01% Shell 0.21%
http

laminas-http's People

Contributors

akrabat avatar bakura10 avatar boesing avatar clemenssahs avatar dasprid avatar denixport avatar evandotpro avatar ezimuel avatar farazdagi avatar freeaqingme avatar koopzington avatar laminas-bot avatar localheinz avatar maks3w avatar marc-mabe avatar michalbundyra avatar micheh avatar mlocati avatar mwillbanks avatar ocean avatar ocramius avatar prolic avatar ralphschindler avatar samsonasik avatar sgehrig avatar socalnick avatar thinkscape avatar wdalmut avatar weierophinney avatar xerkus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laminas-http's Issues

Laminas client user agent being blocked by some hosts

This isn't a bug report, more of a heads-up that may be useful for others that run into it.

I'm using the Laminas Feed component in a system that previously used ZF, and recently we noticed that some RSS feeds had stopped working โ€“ this could have happened when we switched to Laminas but we only just spotted it.

I have tracked this down using curl to some sites apparently blocking the Laminas user agent, for example, using curl's default user agent (which is curl/7.70.0):

$ curl -I https://meetedgar.com/blog/feed/                                                                                                                        
HTTP/1.1 200 OK
$ curl -I -H 'User-Agent: Laminas\Http\Client' https://meetedgar.com/blog/feed/
HTTP/1.1 403 Forbidden

Further investigation suggests it's the backslashes it doesn't like, as an agent of a\b is rejected the same way. Doubling backslashes (a\\b) in case it was a shell escaping issue doesn't help.

Obviously this rejection isn't within Laminas' control, and I have no idea how widespread this issue is; This particular blog is hosted on cloudflare, but other sites I found on cloudflare didn't block these requests; it's also wordpress, but other wordpress sites don't block it. It can be avoided by simply changing the user agent that Laminas uses, either at runtime, or perhaps in Laminas itself; for example Laminas/Http/Client works.

Promote HTTP Headers as standalone component

Currently there is a lack of HTTP header builder / parser in the PHP community ecosystem.

I suggest move Zend\Http\Header namespace to an standalone component for better reusability.

Actually zendframework/zend-http installs 6 packages, 4 direct dependencies + 2 indirect dependencies.

The proposed roadmap is move the subcomponent files to the new repository under the Composer's name of zendframework/zend-http-headers and make it a requirement of zendframework/zend-http

The following files are excluded from these movement and should be keep in this repo.

  • HeaderLoader A Zend\Loader plugin loader of header files.
  • Headers A headers collection which is not reusable for PSR-7 interfaces

BC Breaks: Generally speaking there is no BC Breaks but Header exceptions won't inherit from Zend\Header\Exception anymore


Originally posted by @Maks3w at zendframework/zend-http#43

Fix Http\Response decode gzip

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: zendframework/zendframework#7386
User: @sokac
Created On: 2015-03-30T21:06:46Z
Updated At: 2015-12-04T20:44:40Z
Body
Reference:

p.s. added test case which breaks old code. However, I'm not sure about its license.



Originally posted by @GeeH at zendframework/zend-http#71

`Zend\Http\Response\PhpEnvironment#sendHeaders()` silently returns - should scream and burn and demand for human sacrifices instead

https://github.com/zendframework/zend-http/blob/b382c6e068a2d9c87b7cf0dfe78c6dd19e4ff197/src/PhpEnvironment/Response.php#L84

To reproduce (sorry if I couldn't do it myself right away - needs a @runInSeparateProcess test):

$response = new \Zend\Http\Response\PhpEnvironment();

echo 'foo;
ob_flush();

// expect an exception here
$response->sendHeaders();

Originally posted by @Ocramius at zendframework/zend-http#138

PHP 8.2 deprecation: dynamic property headersSent in PhpEnvironment\Response

Bug Report

Q A
Version(s) 2.16.1, 2.17.1

Summary

PhpEnvironment\Response::sendHeaders sets a dynamic property $headersSent.

This emits a deprecation notice on PHP 8.2:

Deprecated: Creation of dynamic property Laminas\Http\PhpEnvironment\Response::$headersSent is deprecated in .../laminas/laminas-http/src/PhpEnvironment/Response.php on line 113

The property seems to be unused: the corresponding code that actually checks if headers were sent just uses PHP's headers_sent function and doesn't use this property. The set on line 113 looks to have been accidentally left in place by the long-ago change to use headers_sent which removed the property declaration: e29f84f

Requests with integer header keys and string values cause InvalidArgumentExceptions

  • I was not able to find an open or closed issue matching what I'm seeing.
  • This is not a question. (Questions should be asked on chat (Signup here) or our forums.)

If a user sends a GET request to an endpoint which is run on zend-http and if that GET request has a header key:value pair of the form $integer:$string, (e.g., 2:blah), this will cause the endpoint to throw an InvalidArgumentException.

This appears to be similar to zendframework/zend-http#116 and zendframework/zend-mvc#226 but not quite the same.

Code to reproduce the issue

I don't have a specific code snippet to reproduce this (it affects our entire application) but this seems to be happening because, in zend-http/src/Headers.php, addHeaders() can call addHeaderLine() without a second argument if it sees a header with an integer key:

    public function addHeaders($headers)
    {
        if (! is_array($headers) && ! $headers instanceof Traversable) {
            throw new Exception\InvalidArgumentException(sprintf(
                'Expected array or Traversable; received "%s"',
                (is_object($headers) ? get_class($headers) : gettype($headers))
            ));
        }

        foreach ($headers as $name => $value) {
            if (is_int($name)) {
                if (is_string($value)) {
                    $this->addHeaderLine($value);

Expected results

I'm not sure. Perhaps the header should be ignored? Or that addHeaderLine() should look like $this->addHeaderLine((string)$name, $value);?

Actual results

[18-Nov-2019 16:27:12 UTC] PHP Fatal error:  Uncaught Zend\Http\Header\Exception\InvalidArgumentException: A field name was provided without a field value in <path>/zendframework/zend-http/src/Headers.php:192
Stack trace:
#0 <path>/zendframework/zend-http/src/Headers.php(155): Zend\Http\Headers->addHeaderLine('Mozilla/5.0 (Wi...')
#1 <path>/zendframework/zend-http/src/PhpEnvironment/Request.php(236): Zend\Http\Headers->addHeaders(Array)
#2 <path>/zendframework/zend-http/src/PhpEnvironment/Request.php(84): Zend\Http\PhpEnvironment\Request->setServer(Object(Zend\Stdlib\Parameters))
#3 <path>/zendframework/zend-mvc/src/Service/RequestFactory.php(28): Zend\Http\PhpEnvironment\Request->__construct()
#4 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(703): Zend\Mvc\Service\RequestFactory->__invoke(Object(Zend\ServiceManager\ServiceManager), 'Request', NULL)
#5 <path>/zendframework/zend-mvc-console/src/Service/ConsoleRequestDelegatorFactory.php(34): Zend\ServiceManager\ServiceManager->Zend\ServiceManager\{closure}()
#6 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(738): Zend\Mvc\Console\Service\ConsoleRequestDelegatorFactory->__invoke(Object(Zend\ServiceManager\ServiceManager), 'Request', Object(Closure), NULL)
#7 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(742): Zend\ServiceManager\ServiceManager->Zend\ServiceManager\{closure}(Object(Zend\ServiceManager\ServiceManager), 'Request', Object(Closure), NULL)
#8 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(766): Zend\ServiceManager\ServiceManager->createDelegatorFromName('Request', NULL)
#9 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(200): Zend\ServiceManager\ServiceManager->doCreate('Request')
#10 <path>/zendframework/zend-mvc/src/Service/ApplicationFactory.php(34): Zend\ServiceManager\ServiceManager->get('Request')
#11 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(764): Zend\Mvc\Service\ApplicationFactory->__invoke(Object(Zend\ServiceManager\ServiceManager), 'Application', NULL)
#12 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(200): Zend\ServiceManager\ServiceManager->doCreate('Application')
#13 <path>/zendframework/zend-mvc/src/Application.php(273): Zend\ServiceManager\ServiceManager->get('Application')
#14 <path>/zend.php(21): Zend\Mvc\Application::init(Array)
#15 {main}
Next Zend\ServiceManager\Exception\ServiceNotCreatedException: Service with name "Request" could not be created. Reason: A field name was provided without a field value in <path>/zendframework/zend-servicemanager/src/ServiceManager.php:771
Stack trace:
#0 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(200): Zend\ServiceManager\ServiceManager->doCreate('Request')
#1 <path>/zendframework/zend-mvc/src/Service/ApplicationFactory.php(34): Zend\ServiceManager\ServiceManager->get('Request')
#2 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(764): Zend\Mvc\Service\ApplicationFactory->__invoke(Object(Zend\ServiceManager\ServiceManager), 'Application', NULL)
#3 <path>/zendframework/zend-servicemanager/src/ServiceManager.php(200): Zend\ServiceManager\ServiceManager->doCreate('Application')
#4 <path>/zendframework/zend-mvc/src/Application.php(273): Zend\ServiceManager\ServiceManager->get('Application')
#5 <path>/zend.php(21): Zend\Mvc\Application::init(Array)
#6 {main}
  thrown in <path>/zendframework/zend-servicemanager/src/ServiceManager.php on line 771

Originally posted by @aedelstein at zendframework/zend-http#197

Add support for CSP header directive require-trusted-types-for-csp

Feature Request

Add support for require-trusted-types-for-csp directive in a CSP header.

Q A
New Feature yes
RFC no
BC Break no

Summary

This directive has already been described in w3c standard: https://w3c.github.io/webappsec-trusted-types/dist/spec/#require-trusted-types-for-csp-directive
As far as I understand this change isn't in effect yet, but we are already seeing it being used. And not being supported by Laminas HTTP it leads to the problem where the whole header is treated as GenericHeader (which doesn't support multiple same name headers), not ContentSecurityHeader (supports multiple same name headers) which then causes an error where Laminas tries to use string as an array - see my call stack excerpt below:

2022-01-12 09:12:44.28 : [] operator not supported for strings at vendor/laminas/laminas-http/src/Headers.php:435

#0 vendor/laminas/laminas-feed/src/Reader/Http/LaminasHttpClientDecorator.php(108): Laminas\Http\Headers->toArray()
#1 vendor/laminas/laminas-feed/src/Reader/Http/LaminasHttpClientDecorator.php(52): Laminas\Feed\Reader\Http\LaminasHttpClientDecorator->prepareResponseHeaders()
#2 vendor/laminas/laminas-feed/src/Reader/Reader.php(264): Laminas\Feed\Reader\Http\LaminasHttpClientDecorator->get()

Here is formatted var_dump on the list of the headers I receive which are causing the issue:

array(20) {
    [0]=> array(2) {
        ["name"]=> string(12) "Content-Type"
        ["line"]=> string(44) "Content-Type: application/xml; charset=utf-8"
    }
    [1]=> array(2) {
        ["name"]=> string(4) "Vary"
        ["line"]=> string(52) "Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site"
    }
    [2]=> array(2) {
        ["name"]=> string(13) "Cache-Control"
        ["line"]=> string(61) "Cache-Control: no-cache, no-store, max-age=0, must-revalidate"
    }
    [3]=> array(2) {
        ["name"]=> string(6) "Pragma"
        ["line"]=> string(16) "Pragma: no-cache"
    }
    [4]=> array(2) {
        ["name"]=> string(7) "Expires"
        ["line"]=> string(38) "Expires: Mon, 01 Jan 1990 00:00:00 GMT"
    }
    [5]=> array(2) {
        ["name"]=> string(4) "Date"
        ["line"]=> string(35) "Date: Wed, 12 Jan 2022 22:33:53 GMT"
    }
    [6]=> array(2) {
        ["name"]=> string(25) "Strict-Transport-Security"
        ["line"]=> string(43) "Strict-Transport-Security: max-age=31536000"
    }
    [7]=> array(2) {
        ["name"]=> string(28) "Cross-Origin-Resource-Policy"
        ["line"]=> string(39) "Cross-Origin-Resource-Policy: same-site"
    }
    [8]=> array(2) {
        ["name"]=> string(23) "Content-Security-Policy"
        ["line"]=> string(96) "Content-Security-Policy: require-trusted-types-for 'script';report-uri /_/DotsSplashUi/cspreport"
    }
    [9]=> array(2) {
        ["name"]=> string(23) "Content-Security-Policy"
        ["line"]=> string(187) "Content-Security-Policy: script-src 'report-sample' 'nonce-JVg4F9YP1RBoHHpgHF7C7w' 'unsafe-inline';object-src 'none';base-uri 'self';report-uri /_/DotsSplashUi/cspreport;worker-src 'self'"
    }
    [10]=> array(2) {
        ["name"]=> string(26) "Cross-Origin-Opener-Policy"
        ["line"]=> string(78) "Cross-Origin-Opener-Policy: same-origin-allow-popups; report-to="DotsSplashUi""
    }
    [11]=> array(2) {
        ["name"]=> string(9) "Report-To"
        ["line"]=> string(140) "Report-To: {"group":"DotsSplashUi","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/DotsSplashUi/external"}]}"
    }
    [12]=> object(Laminas\Http\Header\ContentEncoding)#726 (1) {
        ["value":protected]=> string(4) "gzip"
    }
    [13]=> array(2) {
        ["name"]=> string(6) "Server"
        ["line"]=> string(11) "Server: ESF"
    }
    [14]=> array(2) {
        ["name"]=> string(16) "X-XSS-Protection"
        ["line"]=> string(19) "X-XSS-Protection: 0"
    }
    [15]=> array(2) {
        ["name"]=> string(15) "X-Frame-Options"
        ["line"]=> string(27) "X-Frame-Options: SAMEORIGIN"
    }
    [16]=> array(2) {
        ["name"]=> string(22) "X-Content-Type-Options"
        ["line"]=> string(31) "X-Content-Type-Options: nosniff"
    }
    [17]=> array(2) {
        ["name"]=> string(7) "Alt-Svc"
        ["line"]=> string(171) "Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43""
    }
    [18]=> array(2) {
        ["name"]=> string(10) "Connection"
        ["line"]=> string(17) "Connection: close"
    }
    [19]=> object(Laminas\Http\Header\TransferEncoding)#731 (1) {
        ["value":protected]=> string(7) "chunked"
    }
}

Incorrect gzip handler with Zend client adapter

I could be wrong but please do let me know if this is "as intended" as it seems a bit strange to me.

Reported from PHP 5.6 with curl, and Zend Framework 3.0.0

If I use this test code to pull content from (eg example.com)

        $config = ['adapter' => 'Zend\Http\Client\Adapter\Curl',];
        $client = new ZendHttpClient($uri, $config);
        $client->send();
        $response = $client->getResponse();
        $this->assertContains("Domain", $response->getContent());

then the client generates a header with

Accept-Encoding: gzip, deflate
but this will assert with

Failed asserting that Binary String: 0x1f8b08003b81055200038d5441afd ...
If you look at the pcap for the request, this is the raw gzip result (starts at 0x01bb)


        0x0030:  1324 208b 4854 5450 2f31 2e31 2032 3030  .$..HTTP/1.1.200
        0x0040:  204f 4b0d 0a43 6f6e 7465 6e74 2d45 6e63  .OK..Content-Enc
        0x0050:  6f64 696e 673a 2067 7a69 700d 0a41 6363  oding:.gzip..Acc
        0x0060:  6570 742d 5261 6e67 6573 3a20 6279 7465  ept-Ranges:.byte
        0x0070:  730d 0a43 6163 6865 2d43 6f6e 7472 6f6c  s..Cache-Control
        0x0080:  3a20 6d61 782d 6167 653d 3630 3438 3030  :.max-age=604800
        0x0090:  0d0a 436f 6e74 656e 742d 5479 7065 3a20  ..Content-Type:.
        0x00a0:  7465 7874 2f68 746d 6c0d 0a44 6174 653a  text/html..Date:
        0x00b0:  2053 756e 2c20 3138 2053 6570 2032 3031  .Sun,.18.Sep.201
        0x00c0:  3620 3139 3a30 343a 3139 2047 4d54 0d0a  6.19:04:19.GMT..
        0x00d0:  4574 6167 3a20 2233 3539 3637 3036 3531  Etag:."359670651
        0x00e0:  2b67 7a69 7022 0d0a 4578 7069 7265 733a  +gzip"..Expires:
        0x00f0:  2053 756e 2c20 3235 2053 6570 2032 3031  .Sun,.25.Sep.201
        0x0100:  3620 3139 3a30 343a 3139 2047 4d54 0d0a  6.19:04:19.GMT..
        0x0110:  4c61 7374 2d4d 6f64 6966 6965 643a 2046  Last-Modified:.F
        0x0120:  7269 2c20 3039 2041 7567 2032 3031 3320  ri,.09.Aug.2013.
        0x0130:  3233 3a35 343a 3335 2047 4d54 0d0a 5365  23:54:35.GMT..Se
        0x0140:  7276 6572 3a20 4543 5320 2865 7772 2f31  rver:.ECS.(ewr/1
        0x0150:  3542 4429 0d0a 5661 7279 3a20 4163 6365  5BD)..Vary:.Acce
        0x0160:  7074 2d45 6e63 6f64 696e 670d 0a58 2d43  pt-Encoding..X-C
        0x0170:  6163 6865 3a20 4849 540d 0a78 2d65 632d  ache:.HIT..x-ec-
        0x0180:  6375 7374 6f6d 2d65 7272 6f72 3a20 310d  custom-error:.1.
        0x0190:  0a43 6f6e 7465 6e74 2d4c 656e 6774 683a  .Content-Length:
        0x01a0:  2036 3036 0d0a 436f 6e6e 6563 7469 6f6e  .606..Connection
        0x01b0:  3a20 636c 6f73 650d 0a0d 0a1f 8b08 003b  :.close........;
        0x01c0:  8105 5200 038d 5441 afd3 300c beef 5798  ..R...TA..0...W.
        0x01d0:  7201 695d f780 0753 d756 2040 e202 1ce0  r.i]...S.V.@....
        0x01e0:  c231 6bdc d55a 9394 24ed 36a1 f7df 71db  .1k..Z..$.6...q.
        0x01f0:  bdae e5ed 402b b58e 1d7f fe6c c749 9e49  ....@+.....l.I.I
        0x0200:  93fb 738d 507a 5565 8be4 f187 4266 0be0  ..s.PzUe....Bf..
        0x0210:  27f1 e42b cc3e 9f84 aa2b 844f 4609 d249  '..+.>...+.OF..I
        0x0220:  3468 17c3 1685 5e40 5e0a ebd0 a741 e38b  4h....^@^....A..
        0x0230:  7013 4094 4d8c a5f7 7588 bf1b 6ad3 e0a3  [email protected]...
        0x0240:  d11e b50f bbb0 01e4 c32a 0d3c 9e7c d485  .........*.<.|..
        0x0250:  df8e 50b7 90b4 5098 062d e1b1 36d6 4ffc  ..P...P..-..6.O.
        0x0260:  8f24 7d99 4a6c 29c7 b05f 2c81 3479 1255  .$}.Jl).._,.4y.U

I believe that if you send Accept headers including gzip and you get gzipped data back then the getContent() function should correctly handle gzipped data, returning the decoded content without having to override with

        $config = [
            'adapter'     => 'Zend\Http\Client\Adapter\Curl',
            'curloptions' => [
                CURLOPT_ENCODING => '',
            ],
        ];
        $client = new ZendHttpClient($uri, $config);

Originally posted by @pedigree at zendframework/zend-http#89

Request object can't easily be proxied because new headers get overwritten by original request's headers

Originally zendframework/zendframework#6101

I have an action that proxies the current request by modifying the Uri in the original request and then sending it through Zend\Http\Client. However, the new Host header based on the Uri gets overwritten by the original Host header in the original request.

Eg:

//original uri: http://example.com/proxy-request
$request = clone $this->request;
$uri = $request->getUri();
$uri->setHost('target-site.com');
$uri->setPath('/proxy-target');

$client = new Zend\Http\Client();
$response = $client->send($request);

The request ends up being sent to http://example.com/proxy-target instead of http://target-site.com/proxy-target

The new host header gets set up in https://github.com/zendframework/zend-http/blob/master/src/Client.php#L1110

But then gets replaced with the header from the request here:
https://github.com/zendframework/zend-http/blob/master/src/Client.php#L1174

Shouldn't the new headers take precedence over the headers from the request?


Originally posted by @dkmuir at zendframework/zend-http#64

Better distinguishing between runtime problems

Right now all problems in Http\Client::send() will happen in one Client\Adapter\Exception\RuntimeException with a (often sprintf) string and error code 0.

Examples:

throw new AdapterException\RuntimeException('Unable to Connect to ' . $host . ':' . $port);
throw new AdapterException\RuntimeException('Trying to write but we are not connected');

With this Exception I cannot handle useful reactions in my software.

My Feature Request hopefully triggers the discussion to cluster Exceptions or work with Exception::code to distinguish problems.

Zend\Http\Header\AbstractAccept, "undefined offset: 1" for some accept headers

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7352
User: @jurajseffer
Created On: 2015-03-21T13:59:27Z
Updated At: 2015-11-06T21:19:42Z
Body
ZF version tested on: 2.3.3

An application sometimes receives the following (invalid?) accept header:

Accept: text/html,application/xhtml+xml,application/xml;image/png,image/jpeg,image/*;q=0.9,*/*;q=0.8

This causes an "Undefined offset: 1" notice on line 174 in Zend\Http\Header\AbstractAccept

$value = trim($explode[1]);


Originally posted by @GeeH at zendframework/zend-http#73

Inefficient Header Checking

Feature Request

Q A
New Feature no
RFC no
BC Break no

Summary

Given HTTP headers can get quite large these days, with CSP etc, sometimes running into the many thousands of characters, can the following functions be optimised instead of looping one character at a time?

public static function filter($value)
{
$value = (string) $value;
$length = strlen($value);
$string = '';
for ($i = 0; $i < $length; $i += 1) {
$ascii = ord($value[$i]);
// Non-visible, non-whitespace characters
// 9 === horizontal tab
// 32-126, 128-254 === visible
// 127 === DEL
// 255 === null byte
if (($ascii < 32 && $ascii !== 9)
|| $ascii === 127
|| $ascii > 254
) {
continue;
}
$string .= $value[$i];
}
return $string;
}

public static function isValid($value)
{
$value = (string) $value;
$length = strlen($value);
for ($i = 0; $i < $length; $i += 1) {
$ascii = ord($value[$i]);
// Non-visible, non-whitespace characters
// 9 === horizontal tab
// 32-126, 128-254 === visible
// 127 === DEL
// 255 === null byte
if (($ascii < 32 && $ascii !== 9)
|| $ascii === 127
|| $ascii > 254
) {
return false;
}
}
return true;

Zend\Http\Headers::addHeaderLine() doesn't check MultipleHeaderInterface

  • I was not able to find an open or closed issue matching what I'm seeing.
  • This is not a question. (Questions should be asked on chat (Signup here) or our forums.)

Adding a Header not implementing MultipleHeaderInterface via addHeaderLine, will add the Header multiple times. I'd expect it to be/stay unique.

Background: I'm trying to overwrite an already (multiple times) set ContentType Header. Because the Header is set twice via addHeaderLine(), it is present multiple times. But via Headers->get('Content-Type') i only get the first instead of all Content-Type headers. Of cause: because it's not implementing MultipleHeaderInterface. :/

In general i'd expect addHeader() and addHeaderLine() to behave the same way. Or am I wrong? ... Of cause Instanciating the Header for the multiple-check would destrory the "lazy-loading" argument in the docs. :/

If i'm wrong, all ZF-internal Header modifications should use addHeader() instead of addHeaderLine() to prevent this behaviour. In this particular Case Zend\View\Strategy\JsonStrategy adds a Content-Type Header via addHeaderLine() and therefore doesn't re-use/overwrite an already set ContentType Header. May be, this is a bug in zend-view, too.

I'd prefer to implement the "multiple-check" into addHeaderLine().

I'd like to contribute some code but i'm nut sure, what the best/right solution is. ... any thoughts/suggestion?

Code to reproduce the issue

$headers = new Headers();
$headers->addHeader(new ContentType(null, 'text/plain')); // i.e. in Controller
$headers->addHeaderLine('Content-Type', 'text/javascript'); // i.e. in JsonStrategy:134
echo $headers->count().PHP_EOL; 
echo $headers->get('Content-Type)->getMediaType(); 

Expected results

1
text/javascript

Actual results

2
text/plain

Originally posted by @FalkHe at zendframework/zend-http#196

Version 3.0/PSR-7 integration?

Dear ZF Team,

Is there a roadmap or plans for version 3.0 of this component, making it PSR-7 compilant?

Thinking about it, I can see 3 major responsibilities of this repo:

  • HTTP message abstraction
  • header manipulation
  • HTTP client

With zend-diactoros covering "HTTP message", zend-http could be ultimately split into two repositories. A lot of PSR-7 applications could benefit from good header manipulation library (I didn't see anything like this centered around PSR-7 yet), that can be developed without spending time on migrating the client (well, we have Guzzle).

What do you think?

best,


Originally posted by @mtymek at zendframework/zend-http#13

Changed default beauvoir for HTTP Version

BC Break Report

Q A
Version 2.15.0

Summary

After upgrading from Verion 2.14.3 to Version 2.15.0 I could record that requests are sent with HTTP 1.0 instead of HTTP 1.1.

The default HTTP version is set to the const value of \Laminas\Http\Request::VERSION_11 wich is the string '1.1'.
see \Laminas\Http\Client::$config

     'httpversion'     => Request::VERSION_11,

The Curl Adapter compares this string to the float value 1.1 in a typesafe way and will switch the HTTP version to 1.0.
see \Laminas\Http\Client\Adapter\Curl line 445

$curlHttp = $httpVersion === 1.1 ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;

Previous behavior

Requests sended by default with HTTP version 1.1 with the Curl adapter

Current behavior

Requests sended by default with HTTP version 1.0 with the Curl adapter

How to reproduce

Some Code:

<?php

require_once 'vendor/autoload.php';

class CurlWrapper extends \Laminas\Http\Client\Adapter\Curl
{
    public function write($method, $uri, $httpVersion = 1.1, $headers = [], $body = '')
    {
        $result = parent::write($method, $uri, $httpVersion, $headers, $body);

        preg_match(
            '/^.*HTTP\/(.*)\s$/m',
            $result,
            $matches
        );

        // Should be by default 1.1
        $realHttpVersion = $matches[1];

        // $httpVersion is actually a mixed type
        if ($httpVersion == 1.1 && $realHttpVersion != $httpVersion) {
            throw new \LogicException('Unexpected HTTP version sent');
        }

        return $result;
    }

}

$clientDefault = new \Laminas\Http\Client(
    'http://jsonplaceholder.typicode.com/todos/1',
    [
        'adapter' => CurlWrapper::class,
    ]
);
$clientDefault->send(); // throws the "Unexpected HTTP version sent" exception

$clientOverwrittenByConst = new \Laminas\Http\Client(
    'http://jsonplaceholder.typicode.com/todos/1',
    [
        'adapter' => CurlWrapper::class,
        'httpversion' => \Laminas\Http\Request::VERSION_11
    ]
);
$clientOverwrittenByConst->send(); // throws the "Unexpected HTTP version sent" exception

$clientOverwrittenByFloat = new \Laminas\Http\Client(
    'http://jsonplaceholder.typicode.com/todos/1',
    [
        'adapter' => CurlWrapper::class,
        'httpversion' => 1.1
    ]
);
$clientOverwrittenByFloat->send(); // correct behaviour

SetCookie: leading dot in domain

I have an issue with SetCookie::isValidForRequest().
There is an unexpected behavior with cookies, while requesting.
The problem is that SetCookie for "**.**example.com" do not applying to "www.example.com" or even to "example.com".

Due to RFC6265-4.1.2.3 it must pass cookie with domain "**.**example.com" if "www.example.com" or "example.com" is requested.

if ($this->getDomain() && (strrpos($requestDomain, $this->getDomain()) === false)) {
    return false;
}

I saw that SetCookie refers to RFC2109 but it's too old (1997).

Prior to this document (RFC6265), there were at least three descriptions of
cookies: the so-called "Netscape cookie specification" [Netscape],
RFC 2109 [RFC2109], and RFC 2965 [RFC2965].  However, none of these
documents describe how the Cookie and Set-Cookie headers are actually
used on the Internet (see [Kri2001] for historical context).  In
relation to previous IETF specifications of HTTP state management
mechanisms, this document requests the following actions:

1.  Change the status of [RFC2109] to Historic (it has already been
   obsoleted by [RFC2965]).

2.  Change the status of [RFC2965] to Historic.

3.  Indicate that [RFC2965] has been obsoleted by this document.

In cases:

(strrpos("www.example.com", ".example.com") === false)
(strrpos("example.com", ".example.com") === false)

must return false to prevent method return false.


Solution is to use "." . ltrim($domain, ".")

if ($this->getDomain()) {
    if (strrpos(('.' . ltrim($requestDomain, '.')), ('.' . ltrim($this->getDomain(), '.'))) === false) {
        return false;
    }
}

Some tests:

$foo = function($value) {
    return '.' . ltrim($value, '.');
}
# $domain $requested $foo($domain) $foo($requested) will apply
1 test.com test.com .test.com .test.com yes
2 .test.com test.com .test.com .test.com yes
3 www.test.com test.com .www.test.com .test.com no
4 .www.test.com test.com .www.test.com .test.com no
5 faketest.com test.com .faketest.com .test.com no
6 test.com www.test.com .test.com .www.test.com yes
7 .test.com www.test.com .test.com .www.test.com yes
8 www.test.com www.test.com .www.test.com .www.test.com yes
9 .www.test.com www.test.com .www.test.com .www.test.com yes
10 faketest.com www.test.com .faketest.com .www.test.com no
11 test.com foo.test.com .test.com .foo.test.com yes
12 .test.com foo.test.com .test.com .foo.test.com yes
13 www.test.com foo.test.com .www.test.com .foo.test.com no
14 .www.test.com foo.test.com .www.test.com .foo.test.com no
15 faketest.com foo.test.com .faketest.com .foo.test.com no

Originally posted by @lysenkobv at zendframework/zend-http#5

PhpEnvironment request does not preserve fragment

Bug Report

Unsure if this is expected but when you have a request with a fragment the request uri does not preserve the fragment in the resulting request uri. If not I would be happy to try to fix it, but I am unsure if this is may be known and by design.

Summary

Open any url with a fragment like #map and I would expect that the fragment is filled in the request uri.

Current behavior

RequestUri has always a null fragment.

How to reproduce

Open any url with a fragment like #map.

Expected behavior

Fragment is filled with the value set in the request url.

About the ssltransport option for the Socket client

We currently accept these values for the ssltransport option of the Socket client:

  • ssl โ‡’ STREAM_CRYPTO_METHOD_SSLv23_CLIENT
  • sslv2 โ‡’ STREAM_CRYPTO_METHOD_SSLv2_CLIENT
  • sslv3 โ‡’ STREAM_CRYPTO_METHOD_SSLv3_CLIENT
  • tls โ‡’ STREAM_CRYPTO_METHOD_TLS_CLIENT

In particular:

  • STREAM_CRYPTO_METHOD_SSLv23_CLIENT
    • evaluates to SSLv2 / SSLv3 for 5.5.0 โ‰ค PHP โ‰ค 5.6.6 (reference)
    • evaluates to TLSv1.0 / TLSv1.1 / TLSv1.2 for 5.6.7 โ‰ค PHP โ‰ค 7.1.0 (reference)
  • STREAM_CRYPTO_METHOD_TLS_CLIENT
    • evaluates to TLSv1.0 / TLSv1.1 / TLSv1.2 for 5.6.0 โ‰ค PHP โ‰ค 5.6.6
    • evaluates to TLSv1.0 for 5.6.7 โ‰ค PHP โ‰ค 7.1.0

There are some problems with the current implementation:

  • the constant values change between different PHP versions, and that leads to portability problems
  • we allow specifying the version of SSL, but not the version of TLS
  • we don't have a way to allow any protocol (aka the STREAM_CRYPTO_METHOD_ANY_CLIENT option introduced in PHP 5.6.0, which evaluates to SSLv2 / SSLv3 / TLSv1.0 / TLSv1.1 / TLSv1.2)

So, what about defining the following transports?

  • ssl to enable sslv2 and sslv3
  • sslv2 to enable only sslv2
  • sslv3 to enable only sslv3
  • tls to enable tlsv1.0 and tlsv1.1 and tlsv1.2
  • tlsv1.0 to enable only tlsv1.0
  • tlsv1.1 to enable only tlsv1.1
  • tlsv1.2 to enable only tlsv1.2
  • * to enable any kind of connections

Furthermore, because of security issues, more and more websites disable SSL (both SSLv2 and SSLv3), keeping only TLS connections (see for instance what's doing Google).

So, what about switching from ssl to tls as the default transport?


Originally posted by @mlocati at zendframework/zend-http#105

Zend\Uri\Exception\InvalidUriPartException: Host "[IP ADDRESS]" is not valid or is not accepted by Zend\Uri\Http

Bug Report

Q A
Version(s) ^2.7

Summary

Laminas\Uri\Exception\InvalidUriPartException: Host "[IP ADDRESS]" is not valid or is not accepted by Laminas\Uri\Http

Current behavior

When certain requests are made a 500 server error is produced.

This appears to be happening on AWS servers hosting zend framework see:

https://magento.stackexchange.com/questions/299596/getting-error-php-fatal-error-uncaught-zend-uri-exception-invaliduripartexcept

https://serverfault.com/questions/996647/getting-error-php-fatal-error-uncaught-zend-uri-exception-invaliduripartexcept

We are also experiencing the error on our Zend Framework site when a set of requests are being sent from an AWS Security Scanner:

44.225.84.206 - - [10/Mar/2020:13:27:06 +0000] "GET http://[::ffff:a9fe:a9fe]/ HTTP/1.1" 500 - "-" "AWS Security Scanner"

The problem is with these lines of code starting on line 298 in /src/PhpEnvironment/Request.php

            // Check for missinterpreted IPv6-Address
            // Reported at least for Safari on Windows
            if (isset($this->serverParams['SERVER_ADDR']) && preg_match('/^\[[0-9a-fA-F\:]+\]$/', $host)) {
                $host = '[' . $this->serverParams['SERVER_ADDR'] . ']';
                if ($port . ']' == substr($host, strrpos($host, ':') + 1)) {
                    // The last digit of the IPv6-Address has been taken as port
                    // Unset the port so the default port can be used
                    $port = null;
                }
            }

How to reproduce

<?php

use PHPUnit\Framework\TestCase;
//use Zend\Http\PhpEnvironment\Request;
use \Laminas\Http\PhpEnvironment\Request;

class Test extends TestCase
{
    public function testInvalidHost()
    {
        $_SERVER = [
            'SCRIPT_NAME'           => '',
            'SERVER_ADDR'           => '10.0.33.135',
            'SERVER_NAME'           => '[::ffff:a9fe:a9fe]',
       ];
        $request = new Request();

    }

}

Expected behavior

The host should be null, 10.0.33.135, or [::ffff:a9fe:a9fe] not [10.0.33.135]

Unwanted behaviour on Zend\Http\Client eq not thread safe

Hi,

We are using our application with around 25 workers (with beanstalkd and 25 listening proccesses). Most of the time we use the Client object to retrieve external content. And we do mime type checks which uses the headers.

Some of the use cases have setStream used.

And some times it will occur that the stream which is set by Client is the same filename used by another proccesses Client object. So we get a json mime type for an xml file by example.

It seems to go wrong on line 691 in \Zend\Http\Client object where tempnam is used. And as we can read from the documentation it is not really save to use.

http://php.net/manual/en/function.tempnam.php#98232

I suppose we should be using setStream with an own unique filename for each process to be safe. But that would be a workaround on the issue.

Thanks!


Originally posted by @muhammedeminakbulut at zendframework/zend-http#30

Rename Zend/Http/Header/AbstractLocation methods uri() and getUri()

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: zendframework/zendframework#7283
User: @27cm
Created On: 2015-03-01T10:25:28Z
Updated At: 2015-11-06T20:54:28Z
Body
I propose to rename AbstractLocation methods according to Http/Request methods: getUri() to getUriString(), uri() to getUri().

Zend/Http/Request:

/**
 * Return the URI for this request object
 *
 * @return HttpUri
 */
public function getUri()
{
    if ($this->uri === null || is_string($this->uri)) {
        $this->uri = new HttpUri($this->uri);
    }
    return $this->uri;
}

/**
 * Return the URI for this request object as a string
 *
 * @return string
 */
public function getUriString()
{
    if ($this->uri instanceof HttpUri) {
        return $this->uri->toString();
    }
    return $this->uri;
}

Zend/Http/Header/AbstractLocation:

/**
 * Return the URI for this header as an instance of Zend\Uri\Http
 *
 * @return UriInterface
 */
public function uri()
{
    if ($this->uri === null || is_string($this->uri)) {
        $this->uri = UriFactory::factory($this->uri);
    }
    return $this->uri;
}

/**
 * Return the URI for this header
 *
 * @return string
 */
public function getUri()
{
    if ($this->uri instanceof UriInterface) {
        return $this->uri->toString();
    }
    return $this->uri;
}

Comment

User: @liufang
Created On: 2015-03-03T00:40:07Z
Updated At: 2015-03-03T00:40:07Z
Body
Retain only a geturi

$this->getUri(); //return object
(string)$this->getUri(); // string



Originally posted by @GeeH at zendframework/zend-http#74

New helper function to detect AJAX request without X-Request-With header

Feature Request

Q A
New Feature yes
RFC yes
BC Break no

Summary

Nowadays, AJAX libraries tend not to send the X-Requested-With: XMLHttpRequest anymore. Following a discussion in axios/axios#1322, the usage of the Accept header should be preferred. The Accept header has less issues with CORS, as it does not manually need to be whitelisted.

Consequently, the following code present in the Request class is no longer able to detect such AJAX request:

    public function isXmlHttpRequest()
    {
        $header = $this->getHeaders()->get('X_REQUESTED_WITH');
        return false !== $header && $header->getFieldValue() === 'XMLHttpRequest';
    }

What would you think of adding a new method wantsJson() as such:

    public function wantsJson()
    {
        $match = $this->getHeaders()->get('Accept')?->match('application/json');
        return 'application/json' === $match?->getTypeString();
    }

That can distinguish requests from regular browsers from those of AJAX libraries, as browsers tend to have */* in their accept header, but not application/json. Could be a nice feature in 2023.

Open for comments.

PHP 8.1: Countable incompatibility for Headers

Bug Report

Q A
Version(s) 2.14

Summary

PHP Fatal error: During inheritance of Countable: Uncaught Return type of Laminas\Http\Headers::count() should either be compatible with Countable::count(): int, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice

More details can be found here:

https://php.watch/versions/8.1/serializable-deprecated
https://wiki.php.net/rfc/phase_out_serializable

Current behavior

Tests are failing.

How to reproduce

Execute tests with PHP 8.1

Expected behavior

Tests are passing.

PHP 8.0 support

Feature Request

Q A
New Feature yes

Summary

To be prepared for the december release of PHP 8.0, this repository has some additional TODOs to be tested against the new major version.

In order to make this repository compatible, one has to follow these steps:

  • Modify composer.json to provide support for PHP 8.0 by adding the constraint ~8.0.0
  • Modify composer.json to drop support for PHP less than 7.3
  • Modify composer.json to implement phpunit 9.3 which supports PHP 7.3+
  • Modify .travis.yml to ignore platform requirements when installing composer dependencies (simply add --ignore-platform-reqs to COMPOSER_ARGS env variable)
  • Modify .travis.yml to add PHP 8.0 to the matrix (NOTE: Do not allow failures as PHP 8.0 has a feature freeze since 2020-08-04!)
  • Modify source code in case there are incompatibilities with PHP 8.0

http_build_query should specify arg separator

In http client zendframework/zend-http/src/Client.php:1231 $body request is created using http_build_query function.

$body = http_build_query($this->getRequest()->getPost()->toArray());

Problem is that separator can be changed via ini_set('arg_separator.output', 'some other'); and this will break http client. Since "x-www-form-urlencoded" accepts only "&" as a separator, suggest to hardcode it there.

See similar issue:
https://www.drupal.org/node/2372211


Originally posted by @ameoba32 at zendframework/zend-http#33

New features

Feature Request

Q A
New Feature yes
RFC yes/no
BC Break yes/no

Summary

2.15.x merge-up to 2.16.x failed

Bug Report

https://github.com/laminas/laminas-http/runs/7883480166?check_suite_focus=true#step:5:17

Q A
Version(s) 2.15.x
Run laminas/automatic-releases@v1
/usr/bin/docker run --name ghcriolaminasautomaticreleases1_74b30c --label 94859b --workdir /github/workspace --rm -e GITHUB_TOKEN -e SIGNING_SECRET_KEY -e GIT_AUTHOR_NAME -e GIT_AUTHOR_EMAIL -e INPUT_COMMAND-NAME -e HOME -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL -e GITHUB_REF_NAME -e GITHUB_REF_PROTECTED -e GITHUB_REF_TYPE -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e GITHUB_STEP_SUMMARY -e RUNNER_OS -e RUNNER_ARCH -e RUNNER_NAME -e RUNNER_TOOL_CACHE -e RUNNER_TEMP -e RUNNER_WORKSPACE -e ACTIONS_RUNTIME_URL -e ACTIONS_RUNTIME_TOKEN -e ACTIONS_CACHE_URL -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" -v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" -v "/home/runner/work/laminas-http/laminas-http":"/github/workspace" ghcr.io/laminas/automatic-releases:1  "laminas:automatic-releases:create-merge-up-pull-request"

In invariant_violation.php line 16:
                                                     
  [Psl\Exception\InvariantViolationException]        
  Failed to create pull request through GitHub API.  
                                                     

Exception trace:
  at /app/vendor/azjezz/psl/src/Psl/invariant_violation.php:16
 Psl\invariant_violation() at /app/vendor/azjezz/psl/src/Psl/invariant.php:21
 Psl\invariant() at /app/src/Github/Api/V3/CreatePullRequestThroughApiCall.php:65
 Laminas\AutomaticReleases\Github\Api\V3\CreatePullRequestThroughApiCall->__invoke() at /app/src/Application/Command/CreateMergeUpPullRequest.php:110
 Laminas\AutomaticReleases\Application\Command\CreateMergeUpPullRequest->execute() at /app/vendor/symfony/console/Command/Command.php:298
 Symfony\Component\Console\Command\Command->run() at /app/vendor/symfony/console/Application.php:1024
 Symfony\Component\Console\Application->doRunCommand() at /app/vendor/symfony/console/Application.php:299
 Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:171
 Symfony\Component\Console\Application->run() at /app/bin/console.php:170
 Laminas\AutomaticReleases\WebApplication\{closure}() at /app/bin/console.php:171

laminas:automatic-releases:create-merge-up-pull-request

Fairly sure something changed in the GitHub API ๐Ÿค”

Proper URI rendering for Zend/Http

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7479
User: @afeder
Created On: 2015-05-03T15:26:42Z
Updated At: 2015-06-03T17:28:40Z
Body
Fixes #7472.


Comment

User: @Martin-P
Created On: 2015-05-03T17:32:43Z
Updated At: 2015-05-03T17:32:43Z
Body
This PR needs unittests.


Comment

User: @afeder
Created On: 2015-05-03T17:36:07Z
Updated At: 2015-05-03T17:39:57Z
Body
Does any guidance exist as to what kinds of unit tests would be needed?


Comment

User: @samsonasik
Created On: 2015-05-03T18:48:36Z
Updated At: 2015-05-03T18:48:36Z
Body
need rebase


Comment

User: @afeder
Created On: 2015-05-03T18:59:30Z
Updated At: 2015-05-03T18:59:30Z
Body
samsonasik: Indeed. Done.


Comment

User: @afeder
Created On: 2015-05-03T19:24:02Z
Updated At: 2015-05-03T19:24:02Z
Body
I added a test for the issue described in #7472.


Comment

User: @samsonasik
Created On: 2015-05-08T23:46:11Z
Updated At: 2015-05-08T23:46:11Z
Body
need rebase again


Comment

User: @afeder
Created On: 2015-05-09T00:53:00Z
Updated At: 2015-05-09T00:53:00Z
Body
Rebase done.


Comment

User: @Martin-P
Created On: 2015-05-09T12:05:19Z
Updated At: 2015-05-09T12:05:19Z
Body
IMO this needs PR needs many more tests.

There are multiple new methods introduced in Zend\Http\Request which are not tested at all:

  • setOptions
  • setArgSeparator
  • getArgSeparator
  • renderQueryString
  • renderUri

A big chunk of code in Zend\Http\Client has been removed and new code is added, but no tests have been added for this.

The only test which has been added is a very basic query string example. What happens with more advanced query strings? Does that work too or will it fail?


Comment

User: @afeder
Created On: 2015-05-09T15:52:06Z
Updated At: 2015-05-09T16:03:00Z
Body
If you share with me what tests you are looking for I shall add them right away.

The big chunks of code being removed or added is little more than migrating the URI building logic from the Client class into the Request class.

I've found two existing tests pertaining to this code which I've now copied over to the tests for the Request class. I've also expanded the query test to cover the case of multiple query parameters being set, and duplicated this test for the renderUri() and renderQueryString() functions.


Comment

User: @afeder
Created On: 2015-05-12T02:13:32Z
Updated At: 2015-05-12T18:21:12Z
Body
I fixed the exception in the setOptions() function per sasezaki's comment. As for testing the function, none exist for the original code in Client.php, but I added a basic one anyway.



Originally posted by @GeeH at zendframework/zend-http#68

Unexpected post format when sending a post with file upload using Zend\Http\Client

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7449
User: @dranzd
Created On: 2015-04-15T00:19:30Z
Updated At: 2015-11-06T21:20:08Z
Body
PHP version: 5.5.3-1ubuntu2.6
Zend framework version: 2.3.4

With the code below, when sending a POST request with a file upload using Zend\Http\Client, I get a different format of the posted data. Not sure if the current result is intentional or if it's a bug.

<?php

// sample controller for http://localhost/
class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        if ($this->getRequest()->isPost()) {
            var_dump($_POST, $_FILES);

            die();
        }

        $sl = $this->getServiceLocator();

        $client = new \Zend\Http\Client('http://localhost/');

        $client->setMethod('POST');

        // Uncomment the line below and you'll get a different format for the posted
        // data.  Format would be the expected one.
        $client->setFileUpload('/path/to/any/upload-file.jpg', 'name-of-upload');

        $client->setParameterPost([
            'my-posts' => [
                'string-index' => 'value',
                [
                    'key1' => 'value 1',
                    'key2' => 'value 1',
                ],
                [
                    'key1' => 'value 2',
                    'key2' => 'value 2',
                ],
            ],
        ]);

        $response = $client->send();

        echo $response->getBody();
    }
}

Expected POST and FILES should be:

// $_POST
array (size=1)
  'my-posts' => 
    array (size=3)
      'string-index' => string 'value' (length=5)
      0 => 
        array (size=2)
          'key1' => string 'value 1' (length=7)
          'key2' => string 'value 1' (length=7)
      1 => 
        array (size=2)
          'key1' => string 'value 2' (length=7)
          'key2' => string 'value 2' (length=7)
// $_FILES
array (size=1)
  'name-of-upload' => 
    array (size=5)
      'name' => string 'upload-file.jpg' (length=16)
      'type' => string 'image/jpeg' (length=10)
      'tmp_name' => string '/tmp/phpYqX3jM' (length=14)
      'error' => int 0
      'size' => int 1234

But the actual result is:

// $_POST
array (size=1)
  'my-posts' => 
    array (size=5)
      'string-index' => string 'value' (length=5)
      0 => 
        array (size=1)
          'key1' => string 'value 1' (length=7)
      1 => 
        array (size=1)
          'key2' => string 'value 1' (length=7)
      2 => 
        array (size=1)
          'key1' => string 'value 2' (length=7)
      3 => 
        array (size=1)
          'key2' => string 'value 2' (length=7)
// $_FILES
array (size=1)
  'name-of-upload' => 
    array (size=5)
      'name' => string 'upload-file.jpg' (length=16)
      'type' => string 'image/jpeg' (length=10)
      'tmp_name' => string '/tmp/phprFNTsR' (length=14)
      'error' => int 0
      'size' => int 1234

Looking at Zend\Http\Client class's flattenParametersArray method, if the lines

            // Calculate array key
            if ($prefix) {
                if (is_int($name)) {
                    $key = $prefix . '[]';      // this line will change
                } else {
                    $key = $prefix . "[$name]";
                }
            } else {
                $key = $name;
            }

are replaced with

            // Calculate array key
            if ($prefix) {
                if (is_int($name)) {
                    $key = $prefix . "[$name]"; // to this one
                } else {
                    $key = $prefix . "[$name]";
                }
            } else {
                $key = $name;
            }

then the result would the expected one.

Without the update, the body of the request would be:

-----ZENDHTTPCLIENT-556380ca854251ed536ed8e936213c44
Content-Disposition: form-data; name="my-posts[string-index]"

value
-----ZENDHTTPCLIENT-556380ca854251ed536ed8e936213c44
Content-Disposition: form-data; name="my-posts[][key1]"

value 1
-----ZENDHTTPCLIENT-556380ca854251ed536ed8e936213c44
Content-Disposition: form-data; name="my-posts[][key2]"

value 1
-----ZENDHTTPCLIENT-556380ca854251ed536ed8e936213c44
Content-Disposition: form-data; name="my-posts[][key1]"

value 2
-----ZENDHTTPCLIENT-556380ca854251ed536ed8e936213c44
Content-Disposition: form-data; name="my-posts[][key2]"

value 2
-----ZENDHTTPCLIENT-556380ca854251ed536ed8e936213c44
Content-Disposition: form-data; name="name-of-upload"; filename="upload-file.jpg"
Content-Type: image/jpeg; charset=binary

With the update, the body of the request is:

-----ZENDHTTPCLIENT-2c8da0ee60176aac7c8bee807ab68512
Content-Disposition: form-data; name="my-posts[string-index]"

value
-----ZENDHTTPCLIENT-2c8da0ee60176aac7c8bee807ab68512
Content-Disposition: form-data; name="my-posts[0][key1]"

value 1
-----ZENDHTTPCLIENT-2c8da0ee60176aac7c8bee807ab68512
Content-Disposition: form-data; name="my-posts[0][key2]"

value 1
-----ZENDHTTPCLIENT-2c8da0ee60176aac7c8bee807ab68512
Content-Disposition: form-data; name="my-posts[1][key1]"

value 2
-----ZENDHTTPCLIENT-2c8da0ee60176aac7c8bee807ab68512
Content-Disposition: form-data; name="my-posts[1][key2]"

value 2
-----ZENDHTTPCLIENT-2c8da0ee60176aac7c8bee807ab68512
Content-Disposition: form-data; name="name-of-upload"; filename="upload-file.jpg"
Content-Type: image/jpeg; charset=binary

Comment

User: @malukenho
Created On: 2015-04-16T22:01:52Z
Updated At: 2015-04-16T22:01:52Z
Body
@dranzd can you send it as a unit test please?



Originally posted by @GeeH at zendframework/zend-http#70

phpstan fixes

  • some fixes on PHPDoc
  • added checks on types when needed

Changed

  • Throw an exception when unable to uncompress response content
  • Relative URIs are not allowed anymore in Zend\Http\Client.
    Anyway, usage of relative URIs has no sense and no adapter can handle it.

Fixed

  • Fixed Zend\Http\Cookies::getAllCookies(Zend\Http\Cookies::COOKIE_STRING_ARRAY).
  • Fixed Zend\Http\Cookies::getAllCookies(Zend\Http\Cookies::COOKIE_STRING_CONCAT).
  • Fixed use of HTTP URIs with empty path in Request

Originally posted by @thomasvargiu at zendframework/zend-http#157

ChunkedBody decoding with Curl Adapter

Zend\Http\Response::decodeChunkedBody fail with exception when curl adapter decode body itself , BUT it still has "Transfer-Encoding: encoded" header

$client = new \Zend\Http\Client();
$client->setOptions(array( 
   'sslverifypeer' => false,
   'adapter'         => 'Zend\Http\Client\Adapter\Curl',
));
$client->setUri('https://www.truesocialmetrics.com/');
$response = $client->send(); // Error parsing body - doesn't seem to be a chunked message

php: 5.5 , 5.6, 7


Originally posted by @necromant2005 at zendframework/zend-http#19

Zend\Http\Response should treat 410 like a not found

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7368
User: @coogle
Created On: 2015-03-26T00:03:33Z
Updated At: 2015-03-26T15:09:34Z
Body
According to RFC 2616 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) A server has the option of returning a HTTP 410 status code when a resource is not found and moreover the server understands the resource will never return (gone).

From a ZF perspective, it seems to me that the Response::isNotFound() method should return true in this case, and at present it does not. A 410 is a 404, with more insight into the status of the resource.


Comment

User: @Martin-P
Created On: 2015-03-26T13:29:04Z
Updated At: 2015-03-26T13:29:04Z
Body
Sounds like a bad idea IMHO. I understand a 410 can be interpreted as a 404, but is fundamentally different. A 410 indicates the resource existed once and is gone now, a 404 however indicates a resource that was never there at all. Furthermore the term "Not found" is always used in a 404 context, because it is the phrase that belongs to a 404 status code. When you add a 410 to the same description "Not found" (in ZF) you create confusion, because the term which was meant for one specific response code (404) is suddenly used for multiple response codes (404 and 410).

As a compromise a new method isGone() looks like a clean solution to me. That way you can make it easier if someone wants to check if a resource is gone.


Comment

User: @macnibblet
Created On: 2015-03-26T13:33:37Z
Updated At: 2015-03-26T13:33:37Z
Body
I agree with @Martin-P because frankly they are two different things and if the api is so advanced it supports 410 then you should be looking for that specifically.

This is also not that complicated

if ($response->isNotFound() || $response->isGone()) 

Comment

User: @coogle
Created On: 2015-03-26T15:09:34Z
Updated At: 2015-03-26T15:09:34Z
Body
It's not that "can be interpreted" is a 404, the RFC Specifically states that if the server knows the resource was there and is gone it should return 410. 404 Does NOT mean the resource was never there at all, just that simply it cannot find anything at the moment -- they are not two different things but simply the same thing with more detail.

404

The server has not found anything matching the Request-URI. No indication is given of whether the
condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server
knows, through some internally configurable mechanism, that an old resource is permanently
unavailable and has no forwarding address. This status code is commonly used when the server does > not wish to reveal exactly why the request has been refused, or when no other response is applicable.

I will be personally satisfied with an isGone(), but I disagree that isNotFound() returning true on a 404 OR a 410 is somehow confusing or broken behavior. The RFC is pretty explicit on the issue, and the argument that users will be confused when as it was pointed out that this is an "advanced case" is pretty flimsy to me.



Originally posted by @GeeH at zendframework/zend-http#72

Https request validation

Hi ZF team! Just a little thing:

How can I check if the request scheme is https?
Regardless the getScheme() method, does the Request class has a method for that?
I can get the scheme:

$request = $this->request->getUri();
return ($request->getScheme() === 'https');

So, where should I add this validation for make it available for all controllers?
Anyways, would be great add this method on the Request object.

$this->request->isSecure() 
// or maybe
$this->request->isHttps()

Thanks! great project ๐Ÿฅ‡


Originally posted by @diemax at zendframework/zend-http#123

Zend/Http/Request::renderRequestLine() does not take query parameters into account

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7472
User: @afeder
Created On: 2015-04-30T16:03:58Z
Updated At: 2015-11-06T21:33:03Z
Body
The renderRequestLine() function of Zend/Http/Request counterintuitively does not include query parameters set via setQuery() in the request line it returns. Is this intentional or is it something for which a patch would likely be accepted?


Comment

User: @adamlundrigan
Created On: 2015-05-01T13:47:35Z
Updated At: 2015-05-01T13:47:35Z
Body
IMO, this is a bug. Zend\Http\Request::renderRequestLine delegates the building of the URI to Zend\Uri\Uri::toString (unless Zend\Http\Request::$uri is a string, of course), but Zend\Http\Request::setQuery does not pass data through to the Uri object so the two fall out of sync.



Originally posted by @GeeH at zendframework/zend-http#69

Unprintable chars in headers led to response parsing error

The issue was in non-printable symbol between those 2 lines in response from moz blog. I suppose it was added as some kind of bots protection by your cdn provider.

$client = new \Zend\Http\Client();
$client->setOptions(array( 
   'sslverifypeer' => false,
   'adapter'         => 'Zend\Http\Client\Adapter\Curl',
));
$client->setUri('https://moz.com/ugc');
$response = $client->send(); // Invalid header value

The symbol is between: X-Iinfo && X-CDN headers
2015-06-30_0724


Originally posted by @necromant2005 at zendframework/zend-http#20

Psalm integration

Feature Request

Q A
QA yes

Summary

As decided during the Technical-Steering-Committee Meeting on August 3rd, 2020, Laminas wants to implement vimeo/psalm in all packages.

Implementing psalm is quite easy.

Required

  • Create a .psalm.xml.dist in the project root
  • Copy and paste the contents from this psalm.xml.dist
  • Run $ composer require vimeo/psalm
  • Run $ vendor/bin/psalm --set-baseline=psalm-baseline.xml
  • Add a composer script static-analysis with the command psalm --shepherd --stats
  • Add a new line to script: in .travis.yml: - if [[ $TEST_COVERAGE == 'true' ]]; then composer static-analysis ; fi
  • Remove phpstan from the project (phpstan.neon.dist, .travis.yml entry, composer.json require-dev and scripts)
Optional
  • Fix as many psalm errors as possible.

Http Client with Curl / default adapter and transfer-encoding chunked: Error parsing body - doesn't seem to be a chunked message

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7683
User: @lucian303
Created On: 2016-03-09T17:15:25Z
Updated At: 2016-05-03T14:31:29Z
Body
Once I set the adapter to Socket, it works, but with Curl or the default adapter (which I assume is Curl), I get this message on chunked encodings. From looking at other tickets and my own experience with prior Zend versions, it looks like the Socket adapter used to be unable to handle the chunked transfer encoding while the Curl one was able to and now that has flipped in ZF 2.5.3. In 2.2.10 for example, I'm 99.9% sure it's the other way.


Comment

User: @marcelto
Created On: 2016-03-09T19:13:57Z
Updated At: 2016-03-09T19:13:57Z
Body
This may not be relevant but in the past I've found that curl sometimes has trouble with chunked responses. To work around the issue I had to set the HTTP version to 1.0.

curl_setopt($ch, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_0);


Comment

User: @lucian303
Created On: 2016-03-09T19:52:27Z
Updated At: 2016-03-09T19:52:27Z
Body
So I did more investigation and found the culprit in Zend\Http\Client\Adapter\Curl.php:437:

$responseHeaders = preg_replace("/Transfer-Encoding:\s*chunked\\r\\n/", "", $responseHeaders);

Should be:

$responseHeaders = preg_replace("/Transfer-Encoding:\s*chunked\\r\\n/i", "", $responseHeaders);

Notice the 'i' in the regex. It should be a case insensitive match as HTTP headers are case insensitive: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html


Comment

User: @lucian303
Created On: 2016-03-09T20:12:03Z
Updated At: 2016-03-09T20:12:03Z
Body
See pull request: zendframework/zend-http#53


Comment

User: @sgehrig
Created On: 2016-05-03T14:30:18Z
Updated At: 2016-05-03T14:31:29Z
Body
Isn't that a duplicate of zendframework/zend-http#19

Any idea when this would be fixed?



Originally posted by @GeeH at zendframework/zend-http#65

Bad response in Curl::write()

Bug Report

Q A
Version(s) 2.13.0

Summary

I got a bad $this->response from Curl::write() when a response content from an external resource contains:

Response

HTTP/1.1 200 OK
Date: Wed, 23 Oct 2019 02:42:54 GMT
Server: Microsoft-IIS/8.0
Cache-Control: private, max-age=0
Content-Type: application/json; charset=utf-8
X-Powered-By: ASP.NET&lt;http://ASP.NET&gt;
Content-Length: 209
Keep-Alive: timeout=2, max=150
Connection: Keep-Alive

{&quot;d&quot;:&quot;https://lms.vinciworks.com/mayerbrownasia/Shibboleth.sso/Login?target=https%3A%2F%2Flms.vinciworks.com%2Fmayerbrownasia%2Fssologin.aspx&amp;entityID=http%3A%2F%2Ffs.mayerbrown.com%2Fadfs%2Fservices%2Ftrust&lt;https://nam01.safelinks.protection.outlook.com/?url=https%3A%2F%2Flms.vinciworks.com%2Fmayerbrownasia%2FShibboleth.sso%2FLogin%3Ftarget%3Dhttps%253A%252F%252Flms.vinciworks.com%252Fmayerbrownasia%252Fssologin.aspx%26entityID%3Dhttp%253A%252F%252Ffs.mayerbrown.com%252Fadfs%252Fservices%252Ftrust&amp;data=01%7C01%7CEric.Chan%40mayerbrown.com%7C34d86713aef148ccddf808d75c6580e4%7C09131022b7854e6d8d42916975e51262%7C0&amp;sdata=%2B6ra5JwWtis1OZQYulKNjRHS9GjRWw9Py%2BY3VoJIW%2Bo%3D&amp;reserved=0&gt;&quot;}
HTTP/1.1 200 OK
Date: Wed, 23 Oct 2019 03:45:52 GMT
Server: Microsoft-IIS/8.0
Cache-Control: private, max-age=0
Content-Type: application/json; charset=utf-8
X-Powered-By: ASP.NET&lt;http://ASP.NET&gt;
Content-Length: 10
Keep-Alive: timeout=2, max=150
Connection: Keep-Alive

{&quot;d&quot;:null}

Current behavior

Output of $this->response on Curl.php:516 line:

{&quot;d&quot;:&quot;https://lms.vinciworks.com/mayerbrownasia/Shibboleth.sso/Login?target=https%3A%2F%2Flms.vinciworks.com%2Fmayerbrownasia%2Fssologin.aspx&amp;entityID=http%3A%2F%2Ffs.mayerbrown.com%2Fadfs%2Fservices%2Ftrust&lt;https://nam01.safelinks.protection.outlook.com/?url=https%3A%2F%2Flms.vinciworks.com%2Fmayerbrownasia%2FShibboleth.sso%2FLogin%3Ftarget%3Dhttps%253A%252F%252Flms.vinciworks.com%252Fmayerbrownasia%252Fssologin.aspx%26entityID%3Dhttp%253A%252F%252Ffs.mayerbrown.com%252Fadfs%252Fservices%252Ftrust&amp;data=01%7C01%7CEric.Chan%40mayerbrown.com%7C34d86713aef148ccddf808d75c6580e4%7C09131022b7854e6d8d42916975e51262%7C0&amp;sdata=%2B6ra5JwWtis1OZQYulKNjRHS9GjRWw9Py%2BY3VoJIW%2Bo%3D&amp;reserved=0&gt;&quot;}
HTTP/1.1 200 OK
Date: Wed, 23 Oct 2019 03:45:52 GMT
Server: Microsoft-IIS/8.0
Cache-Control: private, max-age=0
Content-Type: application/json; charset=utf-8
X-Powered-By: ASP.NET&lt;http://ASP.NET&gt;
Content-Length: 10
Keep-Alive: timeout=2, max=150
Connection: Keep-Alive

{&quot;d&quot;:null}

Custom Exception types for http

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7498
User: @Ragazzo
Created On: 2015-05-07T14:30:36Z
Updated At: 2015-05-07T14:41:55Z
Body
Is it possible to introduce custom exception types for http package? In particular i think TimedOutException would be great to add in curl adapter. Yet this exceptions checks will be done based on string comparison or error codes as for curl, but this custom exceptions types would be useful. So overall following exceptions should be introduced:

  • Zend\Http\Client\Exception\TimedOutException

Comment

User: @Maks3w
Created On: 2015-05-07T14:34:39Z
Updated At: 2015-05-07T14:34:39Z
Body
Sounds a good enhancement for me. Do you will work on this?


Comment

User: @Ragazzo
Created On: 2015-05-07T14:40:40Z
Updated At: 2015-05-07T14:41:08Z
Body
Yes, but i cant say that i will do it in nearest time, is it ok ?


Comment

User: @Maks3w
Created On: 2015-05-07T14:41:41Z
Updated At: 2015-05-07T14:41:41Z
Body
Contributions are welcome at any moment. Take your time :)



Originally posted by @GeeH at zendframework/zend-http#67

\Zend\Http\Response mbstring.func_overload + utf8 + transfer encoding chunked = corrupt http response parser.

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7653
User: @olegserov
Created On: 2015-12-19T21:04:21Z
Updated At: 2016-02-13T15:33:40Z
Body
I'm using mbstring with mbstring.func_overload = 6 (http://php.net/manual/en/mbstring.overload.php)
I was using zend http client to make http requests. One of my requests failed. This error disappears when I'm switching from Zend\Http\Client\Adapter\Socket to Zend\Http\Client\Adapter\Curl, but anyway it is a nasty error with popular mbstring module. I've debugged it to this simple test case.
As you can see when you use utf8 you get a longer content than when you are using 8bit encoding.
This is what it forgets to remove "\r\n0\r\n\r\n"
Here is the request: log.txt

$responseRaw = file_get_contents('log.txt');

assert(ini_get('mbstring.func_overload') == 6);

mb_internal_encoding('8bit');
$res = \Zend\Http\Response::fromString($responseRaw);
$_8bit = $res->getBody();

mb_internal_encoding('utf8');
$res = \Zend\Http\Response::fromString($responseRaw);
$_utf8 = $res->getBody();

echo "8bit=" . md5($_8bit), "; len=", mb_strlen($_8bit, "8bit"), "\n";
echo "utf8=" . md5($_utf8), "; len=", mb_strlen($_utf8, "8bit"),"\n";
// Outputs
// 8bit=c4c2fe78e727b96adbb1f4cbd1187ff2; len=35001
// utf8=ec9b49502969cfd63e382014a29c3e5f; len=35008


Originally posted by @GeeH at zendframework/zend-http#66

Request object from controller is not usable by RequestInterface and PSR-7

Bug Report

Q A
Version(s) 2.18.0

Summary

Hi!

Class src/Request.php is not comfortable to use by Laminas\Stdlib\RequestInterface interface.
Real object has more public methods in comparison with RequestInterface - for method, uri and others

Do you have any plans to use common PSR interfaces like RequestInterface?

It will be more comfortable to use in this situation.

Current behavior

In code you should use Request class as dependency instead of RequestInterface

How to reproduce

Try to use \Laminas\Mvc\Controller\AbstractController::getRequest() and receive uri by getUri() on it and check code by phpstan

Expected behavior

RequestInterface is suitable with PSR-7

AbstractAccept: Typo in dockblock may break static analysis

Bug Report

There's a typo in the dockblock on line 296 which causes static analyser tools, such as psalm, to error on case sensitive filesystems. (Accept\FieldValuePArt\AcceptFieldValuePart, should be Accept\FieldValuePart\AcceptFieldValuePart)

https://github.com/laminas/laminas-http/blob/master/src/Header/AbstractAccept.php#L296

Q A
Version(s) 2.3.0 - 2.11.*

Summary

static analysis breaks because of typo in dockblock.

Current behavior

static analysis breaks.

How to reproduce

Run psalm on the source (on linux)

Expected behavior

static analysis not to break.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.