// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <Cronet/Cronet.h>
#import <Foundation/Foundation.h>

#include <stdint.h>

#include "TargetConditionals.h"

#include "base/location.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "components/cronet/cronet_buildflags.h"
#include "components/cronet/ios/test/cronet_test_base.h"
#include "components/cronet/ios/test/start_cronet.h"
#include "components/cronet/testing/test_server/test_server.h"
#include "net/base/mac/url_conversions.h"
#include "net/base/net_errors.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/test/quic_simple_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"

#include "url/gurl.h"

namespace {

// The buffer size of the stream for HTTPBodyStream post test.
const NSUInteger kRequestBodyBufferLength = 1024;

// The buffer size of the stream for HTTPBodyStream post test when
// testing the stream buffered data size larger than the net stack internal
// buffer size.
const NSUInteger kLargeRequestBodyBufferLength = 100 * kRequestBodyBufferLength;

// The body data write times for HTTPBodyStream post test.
const NSInteger kRequestBodyWriteTimes = 16;
}

@interface StreamBodyRequestDelegate : NSObject<NSStreamDelegate>
- (void)setOutputStream:(NSOutputStream*)outputStream;
- (NSMutableString*)requestBody;
@end
@implementation StreamBodyRequestDelegate {
  NSOutputStream* _stream;
  NSInteger _count;

  NSMutableString* _requestBody;
}

- (instancetype)init {
  _requestBody = [NSMutableString string];
  return self;
}

- (void)setOutputStream:(NSOutputStream*)outputStream {
  _stream = outputStream;
}

- (NSMutableString*)requestBody {
  return _requestBody;
}

- (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)event {
  ASSERT_EQ(stream, _stream);
  switch (event) {
    case NSStreamEventHasSpaceAvailable: {
      if (_count < kRequestBodyWriteTimes) {
        uint8_t buffer[kRequestBodyBufferLength];
        memset(buffer, 'a' + _count, kRequestBodyBufferLength);
        NSUInteger bytes_write =
            [_stream write:buffer maxLength:kRequestBodyBufferLength];
        ASSERT_EQ(kRequestBodyBufferLength, bytes_write);
        [_requestBody appendString:[[NSString alloc]
                                       initWithBytes:buffer
                                              length:kRequestBodyBufferLength
                                            encoding:NSUTF8StringEncoding]];
        ++_count;
      } else {
        [_stream close];
      }
      break;
    }
    case NSStreamEventErrorOccurred:
    case NSStreamEventEndEncountered: {
      [_stream close];
      [_stream setDelegate:nil];
      [_stream removeFromRunLoop:[NSRunLoop currentRunLoop]
                         forMode:NSDefaultRunLoopMode];
      break;
    }
    default:
      break;
  }
}
@end

namespace cronet {
const char kUserAgent[] = "CronetTest/1.0.0.0";

class HttpTest : public CronetTestBase {
 protected:
  HttpTest() {}
  ~HttpTest() override {}

  void SetUp() override {
    CronetTestBase::SetUp();
    TestServer::Start();

    [Cronet setRequestFilterBlock:^(NSURLRequest* request) {
      return YES;
    }];
    StartCronet(net::QuicSimpleTestServer::GetPort());
    [Cronet registerHttpProtocolHandler];
    NSURLSessionConfiguration* config =
        [NSURLSessionConfiguration ephemeralSessionConfiguration];
    config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
    [Cronet installIntoSessionConfiguration:config];
    session_ = [NSURLSession sessionWithConfiguration:config
                                             delegate:delegate_
                                        delegateQueue:nil];
  }

  void TearDown() override {
    TestServer::Shutdown();

    [Cronet stopNetLog];
    [Cronet shutdownForTesting];
    CronetTestBase::TearDown();
  }

  NSURLSession* session_;
};

TEST_F(HttpTest, CreateSslKeyLogFile) {
  // Shutdown Cronet so that it can be restarted with specific configuration
  // (SSL key log file specified in experimental options) for this one test.
  // This is necessary because SslKeyLogFile can only be set once, before any
  // SSL Client Sockets are created.

  [Cronet shutdownForTesting];

  NSString* ssl_key_log_file = [Cronet getNetLogPathForFile:@"SSLKEYLOGFILE"];

  // Ensure that the keylog file doesn't exist.
  [[NSFileManager defaultManager] removeItemAtPath:ssl_key_log_file error:nil];

  [Cronet setExperimentalOptions:
              [NSString stringWithFormat:@"{\"ssl_key_log_file\":\"%@\"}",
                                         ssl_key_log_file]];

  StartCronet(net::QuicSimpleTestServer::GetPort());

  bool ssl_file_created =
      [[NSFileManager defaultManager] fileExistsAtPath:ssl_key_log_file];

  [[NSFileManager defaultManager] removeItemAtPath:ssl_key_log_file error:nil];

  [Cronet shutdownForTesting];
  [Cronet setExperimentalOptions:@""];

  EXPECT_TRUE(ssl_file_created);
}

TEST_F(HttpTest, NSURLSessionReceivesData) {
  NSURL* url = net::NSURLWithGURL(net::QuicSimpleTestServer::GetSimpleURL());
  __block BOOL block_used = NO;
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  [Cronet setRequestFilterBlock:^(NSURLRequest* request) {
    block_used = YES;
    EXPECT_EQ([request URL], url);
    return YES;
  }];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_TRUE(block_used);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_EQ(net::QuicSimpleTestServer::GetSimpleBodyValue(),
            base::SysNSStringToUTF8([delegate_ responseBody]));
}

// https://crbug.com/830005 Disable histogram support to reduce binary size.
#if BUILDFLAG(DISABLE_HISTOGRAM_SUPPORT)
#define MAYBE_GetGlobalMetricsDeltas DISABLED_GetGlobalMetricsDeltas
#else  // BUILDFLAG(DISABLE_HISTOGRAM_SUPPORT)
#define MAYBE_GetGlobalMetricsDeltas GetGlobalMetricsDeltas
#endif  // BUILDFLAG(DISABLE_HISTOGRAM_SUPPORT)
TEST_F(HttpTest, MAYBE_GetGlobalMetricsDeltas) {
  NSData* delta1 = [Cronet getGlobalMetricsDeltas];
  NSURL* url = net::NSURLWithGURL(net::QuicSimpleTestServer::GetSimpleURL());
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_EQ(net::QuicSimpleTestServer::GetSimpleBodyValue(),
            base::SysNSStringToUTF8([delegate_ responseBody]));

  NSData* delta2 = [Cronet getGlobalMetricsDeltas];
  EXPECT_FALSE([delta2 isEqualToData:delta1]);
}

TEST_F(HttpTest, SdchDisabledByDefault) {
  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("Accept-Encoding")));
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_FALSE([[delegate_ responseBody] containsString:@"sdch"]);
}

// Verify that explictly setting Accept-Encoding request header to 'gzip,sdch"
// is passed to the server and does not trigger any failures. This behavior may
// In the future Cronet may not allow caller to set Accept-Encoding header and
// could limit it to set of internally suported and enabled encodings, matching
// behavior of Cronet on Android.
TEST_F(HttpTest, AcceptEncodingSdchIsAllowed) {
  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("Accept-Encoding")));
  NSMutableURLRequest* mutableRequest =
      [[NSURLRequest requestWithURL:url] mutableCopy];
  [mutableRequest addValue:@"gzip,sdch" forHTTPHeaderField:@"Accept-Encoding"];
  NSURLSessionDataTask* task = [session_ dataTaskWithRequest:mutableRequest];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:@"gzip,sdch"]);
}

// Verify that explictly setting Accept-Encoding request header to 'foo,bar"
// is passed to the server and does not trigger any failures. This behavior may
// In the future Cronet may not allow caller to set Accept-Encoding header and
// could limit it to set of internally suported and enabled encodings, matching
// behavior of Cronet on Android.
TEST_F(HttpTest, AcceptEncodingFooBarIsAllowed) {
  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("Accept-Encoding")));
  NSMutableURLRequest* mutableRequest =
      [[NSURLRequest requestWithURL:url] mutableCopy];
  [mutableRequest addValue:@"foo,bar" forHTTPHeaderField:@"Accept-Encoding"];
  NSURLSessionDataTask* task = [session_ dataTaskWithRequest:mutableRequest];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:@"foo,bar"]);
}

TEST_F(HttpTest, NSURLSessionAcceptLanguage) {
  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("Accept-Language")));
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  ASSERT_STREQ("en-US,en",
               base::SysNSStringToUTF8([delegate_ responseBody]).c_str());
}

TEST_F(HttpTest, SetUserAgentIsExact) {
  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("User-Agent")));
  [Cronet setRequestFilterBlock:nil];
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_STREQ(kUserAgent,
               base::SysNSStringToUTF8([delegate_ responseBody]).c_str());
}

TEST_F(HttpTest, SetUserAgentIsAllowed) {
  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("User-Agent")));
  NSMutableURLRequest* mutableRequest =
      [[NSURLRequest requestWithURL:url] mutableCopy];
  [mutableRequest addValue:@"foo,bar" forHTTPHeaderField:@"User-Agent"];
  NSURLSessionDataTask* task = [session_ dataTaskWithRequest:mutableRequest];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:@"foo,bar"]);

  // Now check to see if the User-Agent string is restored to default when
  // creating a new task from a request.
  mutableRequest = [[NSURLRequest requestWithURL:url] mutableCopy];
  task = [session_ dataTaskWithRequest:mutableRequest];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_STREQ(kUserAgent,
               base::SysNSStringToUTF8([delegate_ responseBody]).c_str());
}

TEST_F(HttpTest, SetCookie) {
  const char kCookieHeader[] = "Cookie";
  NSString* cookieName =
      [NSString stringWithFormat:@"SetCookie-%@", [[NSUUID UUID] UUIDString]];
  NSString* cookieValue = [[NSUUID UUID] UUIDString];
  NSString* cookieLine =
      [NSString stringWithFormat:@"%@=%@", cookieName, cookieValue];
  NSHTTPCookieStorage* systemCookieStorage =
      [NSHTTPCookieStorage sharedHTTPCookieStorage];
  NSURL* cookieUrl =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL(kCookieHeader)));
  // Verify that cookie is not set in system storage.
  for (NSHTTPCookie* cookie in [systemCookieStorage cookiesForURL:cookieUrl]) {
    EXPECT_FALSE([[cookie name] isEqualToString:cookieName]);
  }

  StartDataTaskAndWaitForCompletion([session_ dataTaskWithURL:cookieUrl]);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_STREQ("Header not found. :(",
               base::SysNSStringToUTF8([delegate_ responseBody]).c_str());

  NSURL* setCookieUrl = net::NSURLWithGURL(
      GURL(TestServer::GetSetCookieURL(base::SysNSStringToUTF8(cookieLine))));
  StartDataTaskAndWaitForCompletion([session_ dataTaskWithURL:setCookieUrl]);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:cookieLine]);

  StartDataTaskAndWaitForCompletion([session_ dataTaskWithURL:cookieUrl]);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:cookieLine]);

  // Verify that cookie is set in system storage.
  NSHTTPCookie* systemCookie = nil;
  for (NSHTTPCookie* cookie in [systemCookieStorage cookiesForURL:cookieUrl]) {
    if ([cookie.name isEqualToString:cookieName]) {
      systemCookie = cookie;
      break;
    }
  }
  EXPECT_TRUE([[systemCookie value] isEqualToString:cookieValue]);
  [systemCookieStorage deleteCookie:systemCookie];
}

TEST_F(HttpTest, SetSystemCookie) {
  const char kCookieHeader[] = "Cookie";
  NSString* cookieName = [NSString
      stringWithFormat:@"SetSystemCookie-%@", [[NSUUID UUID] UUIDString]];
  NSString* cookieValue = [[NSUUID UUID] UUIDString];
  NSHTTPCookieStorage* systemCookieStorage =
      [NSHTTPCookieStorage sharedHTTPCookieStorage];
  NSURL* echoCookieUrl =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL(kCookieHeader)));
  NSHTTPCookie* systemCookie = [NSHTTPCookie cookieWithProperties:@{
    NSHTTPCookiePath : [echoCookieUrl path],
    NSHTTPCookieName : cookieName,
    NSHTTPCookieValue : cookieValue,
    NSHTTPCookieDomain : [echoCookieUrl host],
  }];
  [systemCookieStorage setCookie:systemCookie];

  StartDataTaskAndWaitForCompletion([session_ dataTaskWithURL:echoCookieUrl]);
  [systemCookieStorage deleteCookie:systemCookie];
  EXPECT_EQ(nil, [delegate_ error]);
  // Verify that cookie set in system store was sent to the serever.
  EXPECT_TRUE([[delegate_ responseBody] containsString:cookieName]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:cookieValue]);
}

TEST_F(HttpTest, SystemCookieWithNullCreationTime) {
  const char kCookieHeader[] = "Cookie";
  NSString* cookieName = [NSString
      stringWithFormat:@"SetSystemCookie-%@", [[NSUUID UUID] UUIDString]];
  NSString* cookieValue = [[NSUUID UUID] UUIDString];
  NSHTTPCookieStorage* systemCookieStorage =
      [NSHTTPCookieStorage sharedHTTPCookieStorage];
  NSURL* echoCookieUrl =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL(kCookieHeader)));
  NSHTTPCookie* nullCreationTimeCookie = [NSHTTPCookie cookieWithProperties:@{
    NSHTTPCookiePath : [echoCookieUrl path],
    NSHTTPCookieName : cookieName,
    NSHTTPCookieValue : cookieValue,
    NSHTTPCookieDomain : [echoCookieUrl host],
    @"Created" : [NSNumber numberWithDouble:0.0],
  }];
  [systemCookieStorage setCookie:nullCreationTimeCookie];
  NSHTTPCookie* normalCookie = [NSHTTPCookie cookieWithProperties:@{
    NSHTTPCookiePath : [echoCookieUrl path],
    NSHTTPCookieName : [cookieName stringByAppendingString:@"-normal"],
    NSHTTPCookieValue : cookieValue,
    NSHTTPCookieDomain : [echoCookieUrl host],
  }];
  [systemCookieStorage setCookie:normalCookie];
  StartDataTaskAndWaitForCompletion([session_ dataTaskWithURL:echoCookieUrl]);
  [systemCookieStorage deleteCookie:nullCreationTimeCookie];
  [systemCookieStorage deleteCookie:normalCookie];
  EXPECT_EQ(nil, [delegate_ error]);
  // Verify that cookie set in system store was sent to the serever.
  EXPECT_TRUE([[delegate_ responseBody] containsString:cookieName]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:cookieValue]);
}

TEST_F(HttpTest, FilterOutRequest) {
  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("User-Agent")));
  __block BOOL block_used = NO;
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  [Cronet setRequestFilterBlock:^(NSURLRequest* request) {
    block_used = YES;
    EXPECT_EQ([request URL], url);
    return NO;
  }];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_TRUE(block_used);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_FALSE([[delegate_ responseBody]
      containsString:base::SysUTF8ToNSString(kUserAgent)]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:@"CFNetwork"]);
}

TEST_F(HttpTest, FileSchemeNotSupported) {
  NSString* fileData = @"Hello, World!";
  NSString* documentsDirectory = [NSSearchPathForDirectoriesInDomains(
      NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  NSString* filePath = [documentsDirectory
      stringByAppendingPathComponent:[[NSProcessInfo processInfo]
                                         globallyUniqueString]];
  [fileData writeToFile:filePath
             atomically:YES
               encoding:NSUTF8StringEncoding
                  error:nil];

  NSURL* url = [NSURL fileURLWithPath:filePath];
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  [Cronet setRequestFilterBlock:^(NSURLRequest* request) {
    [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
    EXPECT_TRUE(false) << "Block should not be called for unsupported requests";
    return YES;
  }];
  StartDataTaskAndWaitForCompletion(task);
  [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:fileData]);
}

TEST_F(HttpTest, DataSchemeNotSupported) {
  NSString* testString = @"Hello, World!";
  NSData* testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
  NSString* dataString =
      [NSString stringWithFormat:@"data:text/plain;base64,%@",
                                 [testData base64EncodedStringWithOptions:0]];
  NSURL* url = [NSURL URLWithString:dataString];
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  [Cronet setRequestFilterBlock:^(NSURLRequest* request) {
    EXPECT_TRUE(false) << "Block should not be called for unsupported requests";
    return YES;
  }];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:testString]);
}

TEST_F(HttpTest, BrotliAdvertisedTest) {
  [Cronet shutdownForTesting];

  [Cronet setBrotliEnabled:YES];

  StartCronet(net::QuicSimpleTestServer::GetPort());

  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("Accept-Encoding")));
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_TRUE([[delegate_ responseBody] containsString:@"br"]);
}

TEST_F(HttpTest, BrotliNotAdvertisedTest) {
  [Cronet shutdownForTesting];

  [Cronet setBrotliEnabled:NO];

  StartCronet(net::QuicSimpleTestServer::GetPort());

  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetEchoHeaderURL("Accept-Encoding")));
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_FALSE([[delegate_ responseBody] containsString:@"br"]);
}

TEST_F(HttpTest, BrotliHandleDecoding) {
  [Cronet shutdownForTesting];

  [Cronet setBrotliEnabled:YES];

  StartCronet(net::QuicSimpleTestServer::GetPort());

  NSURL* url =
      net::NSURLWithGURL(GURL(TestServer::GetUseEncodingURL("brotli")));
  NSURLSessionDataTask* task = [session_ dataTaskWithURL:url];
  StartDataTaskAndWaitForCompletion(task);
  EXPECT_EQ(nil, [delegate_ error]);
  EXPECT_STREQ(base::SysNSStringToUTF8([delegate_ responseBody]).c_str(),
               "The quick brown fox jumps over the lazy dog");
}

TEST_F(HttpTest, PostRequest) {
  // Create request body.
  NSString* request_body = [NSString stringWithFormat:@"Post Data %i", rand()];
  NSData* post_data = [request_body dataUsingEncoding:NSUTF8StringEncoding];

  // Prepare the request.
  NSURL* url = net::NSURLWithGURL(GURL(TestServer::GetEchoRequestBodyURL()));
  NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
  request.HTTPMethod = @"POST";
  request.HTTPBody = post_data;

  // Set the request filter to check that the request was handled by the Cronet
  // stack.
  __block BOOL block_used = NO;
  [Cronet setRequestFilterBlock:^(NSURLRequest* req) {
    block_used = YES;
    EXPECT_EQ([req URL], url);
    return YES;
  }];

  // Send the request and wait for the response.
  NSURLSessionDataTask* data_task = [session_ dataTaskWithRequest:request];
  StartDataTaskAndWaitForCompletion(data_task);

  // Verify that the response from the server matches the request body.
  NSString* response_body = [delegate_ responseBody];
  ASSERT_EQ(nil, [delegate_ error]);
  ASSERT_STREQ(base::SysNSStringToUTF8(request_body).c_str(),
               base::SysNSStringToUTF8(response_body).c_str());
  ASSERT_TRUE(block_used);
}

TEST_F(HttpTest, PostRequestWithLargeBody) {
  // Create request body.
  std::string request_body(kLargeRequestBodyBufferLength, 'z');
  NSData* post_data = [NSData dataWithBytes:request_body.c_str()
                                     length:request_body.length()];

  // Prepare the request.
  NSURL* url = net::NSURLWithGURL(GURL(TestServer::GetEchoRequestBodyURL()));
  NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
  request.HTTPMethod = @"POST";
  request.HTTPBody = post_data;

  // Set the request filter to check that the request was handled by the Cronet
  // stack.
  __block BOOL block_used = NO;
  [Cronet setRequestFilterBlock:^(NSURLRequest* req) {
    block_used = YES;
    EXPECT_EQ([req URL], url);
    return YES;
  }];

  // Send the request and wait for the response.
  NSURLSessionDataTask* data_task = [session_ dataTaskWithRequest:request];
  StartDataTaskAndWaitForCompletion(data_task);

  // Verify that the response from the server matches the request body.
  NSString* response_body = [delegate_ responseBody];
  ASSERT_EQ(nil, [delegate_ error]);
  ASSERT_STREQ(request_body.c_str(),
               base::SysNSStringToUTF8(response_body).c_str());
  ASSERT_TRUE(block_used);
}

// Verify the chunked request body upload function.
TEST_F(HttpTest, PostRequestWithBodyStream) {
  // Create request body stream.
  CFReadStreamRef read_stream = NULL;
  CFWriteStreamRef write_stream = NULL;
  CFStreamCreateBoundPair(NULL, &read_stream, &write_stream,
                          kRequestBodyBufferLength);

  NSInputStream* input_stream = CFBridgingRelease(read_stream);
  NSOutputStream* output_stream = CFBridgingRelease(write_stream);

  StreamBodyRequestDelegate* stream_delegate =
      [[StreamBodyRequestDelegate alloc] init];
  output_stream.delegate = stream_delegate;
  [stream_delegate setOutputStream:output_stream];

  dispatch_queue_t queue =
      dispatch_queue_create("data upload queue", DISPATCH_QUEUE_SERIAL);
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  dispatch_async(queue, ^{
    [output_stream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                             forMode:NSDefaultRunLoopMode];
    [output_stream open];

    [[NSRunLoop currentRunLoop]
        runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];

    dispatch_semaphore_signal(semaphore);
  });

  // Prepare the request.
  NSURL* url = net::NSURLWithGURL(GURL(TestServer::GetEchoRequestBodyURL()));
  NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
  request.HTTPMethod = @"POST";
  request.HTTPBodyStream = input_stream;

  // Set the request filter to check that the request was handled by the Cronet
  // stack.
  __block BOOL block_used = NO;
  [Cronet setRequestFilterBlock:^(NSURLRequest* req) {
    block_used = YES;
    EXPECT_EQ([req URL], url);
    return YES;
  }];

  // Send the request and wait for the response.
  NSURLSessionDataTask* data_task = [session_ dataTaskWithRequest:request];
  StartDataTaskAndWaitForCompletion(data_task);

  // Verify that the response from the server matches the request body.
  ASSERT_EQ(nil, [delegate_ error]);
  NSString* response_body = [delegate_ responseBody];
  NSMutableString* request_body = [stream_delegate requestBody];
  ASSERT_STREQ(base::SysNSStringToUTF8(request_body).c_str(),
               base::SysNSStringToUTF8(response_body).c_str());
  ASSERT_TRUE(block_used);

  // Wait for the run loop of the child thread exits. Timeout is 5 seconds.
  dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
  ASSERT_EQ(0, dispatch_semaphore_wait(semaphore, timeout));
}

// Verify that the chunked data uploader can correctly handle the request body
// if the stream contains data length exceed the internal upload buffer.
TEST_F(HttpTest, PostRequestWithLargeBodyStream) {
  // Create request body stream.
  CFReadStreamRef read_stream = NULL;
  CFWriteStreamRef write_stream = NULL;
  // 100KB data is written in one time.
  CFStreamCreateBoundPair(NULL, &read_stream, &write_stream,
                          kLargeRequestBodyBufferLength);

  NSInputStream* input_stream = CFBridgingRelease(read_stream);
  NSOutputStream* output_stream = CFBridgingRelease(write_stream);
  [output_stream open];

  uint8_t buffer[kLargeRequestBodyBufferLength];
  memset(buffer, 'a', kLargeRequestBodyBufferLength);
  NSUInteger bytes_write =
      [output_stream write:buffer maxLength:kLargeRequestBodyBufferLength];
  ASSERT_EQ(kLargeRequestBodyBufferLength, bytes_write);
  [output_stream close];

  // Prepare the request.
  NSURL* url = net::NSURLWithGURL(GURL(TestServer::GetEchoRequestBodyURL()));
  NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
  request.HTTPMethod = @"POST";
  request.HTTPBodyStream = input_stream;

  // Set the request filter to check that the request was handled by the Cronet
  // stack.
  __block BOOL block_used = NO;
  [Cronet setRequestFilterBlock:^(NSURLRequest* req) {
    block_used = YES;
    EXPECT_EQ([req URL], url);
    return YES;
  }];

  // Send the request and wait for the response.
  NSURLSessionDataTask* data_task = [session_ dataTaskWithRequest:request];
  StartDataTaskAndWaitForCompletion(data_task);

  // Verify that the response from the server matches the request body.
  ASSERT_EQ(nil, [delegate_ error]);
  NSString* response_body = [delegate_ responseBody];
  ASSERT_EQ(kLargeRequestBodyBufferLength, [response_body length]);
  ASSERT_TRUE(block_used);
}

// iOS Simulator doesn't support changing thread priorities.
// Therefore, run these tests only on a physical device.
#if TARGET_OS_SIMULATOR
#define MAYBE_ChangeThreadPriorityAfterStart \
  DISABLED_ChangeThreadPriorityAfterStart
#define MAYBE_ChangeThreadPriorityBeforeStart \
  DISABLED_ChangeThreadPriorityBeforeStart
#else
#define MAYBE_ChangeThreadPriorityAfterStart ChangeThreadPriorityAfterStart
#define MAYBE_ChangeThreadPriorityBeforeStart ChangeThreadPriorityBeforeStart
#endif  // TARGET_OS_SIMULATOR

// Tests that the network thread priority can be changed after
// Cronet has been started.
TEST_F(HttpTest, MAYBE_ChangeThreadPriorityAfterStart) {
  // Get current (default) priority of the network thread.
  __block double default_priority;
  PostBlockToNetworkThread(FROM_HERE, ^{
    default_priority = NSThread.threadPriority;
  });

  // Modify the network thread priority.
  const double new_priority = 1.0;
  [Cronet setNetworkThreadPriority:new_priority];

  // Get modified priority of the network thread.
  dispatch_semaphore_t lock = dispatch_semaphore_create(0);
  __block double actual_priority;
  PostBlockToNetworkThread(FROM_HERE, ^{
    actual_priority = NSThread.threadPriority;
    dispatch_semaphore_signal(lock);
  });

  // Wait until the posted tasks are completed.
  dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);

  EXPECT_EQ(0.5, default_priority);

  // Check that the priority was modified and is close to the set priority.
  EXPECT_TRUE(abs(actual_priority - new_priority) < 0.01)
      << "Unexpected thread priority. Expected " << new_priority << " but got "
      << actual_priority;
}

// Tests that the network thread priority can be changed before
// Cronet has been started.
TEST_F(HttpTest, MAYBE_ChangeThreadPriorityBeforeStart) {
  // Start a new Cronet engine modifying the network thread priority before the
  // start.
  [Cronet shutdownForTesting];
  const double new_priority = 0.8;
  [Cronet setNetworkThreadPriority:new_priority];
  [Cronet start];

  // Get modified priority of the network thread.
  dispatch_semaphore_t lock = dispatch_semaphore_create(0);
  __block double actual_priority;
  PostBlockToNetworkThread(FROM_HERE, ^{
    actual_priority = NSThread.threadPriority;
    dispatch_semaphore_signal(lock);
  });

  // Wait until the posted task is completed.
  dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);

  // Check that the priority was modified and is close to the set priority.
  EXPECT_TRUE(abs(actual_priority - new_priority) < 0.01)
      << "Unexpected thread priority. Expected " << new_priority << " but got "
      << actual_priority;
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
TEST_F(HttpTest, LegacyApi) {
  NSURL* url = net::NSURLWithGURL(net::QuicSimpleTestServer::GetSimpleURL());

  __block BOOL block_used = NO;
  [Cronet setRequestFilterBlock:^(NSURLRequest* request) {
    block_used = YES;
    EXPECT_EQ(request.URL, url);
    return YES;
  }];

  NSURLRequest* request = [NSURLRequest requestWithURL:url];
  NSError* err;
  NSHTTPURLResponse* response;
  [NSURLConnection sendSynchronousRequest:request
                        returningResponse:&response
                                    error:&err];

  EXPECT_EQ(200, [response statusCode]);
  EXPECT_TRUE(block_used);
  EXPECT_FALSE(err);
}
#pragma clang diagnostic pop

}  // namespace cronet
