<commit_before>#include "RawTextureData.hpp"
#include "../StreamWriter.hpp"
#include "../StreamReader.hpp"
#include "../MemoryFile.hpp"
#include "../Exception.hpp"
#include <algorithm>

BEGIN_INANITY_GRAPHICS

RawTextureData::RawTextureData(ptr<File> pixels, PixelFormat format, int width, int height, int depth, int mips, int count)
: pixels(pixels), format(format), width(width), height(height), depth(depth), mips(mips), count(count)
{
	try
	{
		// check params
		if(width <= 0)
			THROW("Width should be > 0");
		if(height < 0)
			THROW("Height should be >= 0");
		if(height == 0)
		{
			if(depth != 0)
				THROW("Depth != 0, but height == 0");
		}
		else
		{
			if(depth < 0)
				THROW("Depth should be >= 0");
		}
		if(mips <= 0)
			THROW("Mips should be > 0");
		if(count < 0)
			THROW("Count should be >= 0");

		// get a maximum dimension of image
		int maxDimension = std::max(std::max(width, height), depth);

		// calculate mip offsets
		// we use a floor-scheme, as it used in OpenGL
		mipOffsets.resize(mips);
		int mipOffset = 0;
		for(int i = 0; i < mips; ++i)
		{
			// if we reach the zero dimension, mips was too big
			if(maxDimension == 0)
				THROW("Too much mips");

			// store current offset
			mipOffsets[i] = mipOffset;

			// calculate size of the current mip
			int mipSize = GetMipSize(i);
			// advance offset by current mip size
			mipOffset += mipSize;

			// max dimension of next mip
			maxDimension /= 2;
		}

		// store the size of one image
		arrayPitch = mipOffset;

		// check the size of file (or create it if doesn't created yet)
		if(pixels)
		{
			if((int)pixels->GetSize() != arrayPitch * (count ? count : 1))
				THROW("Wrong file size");
		}
		else
			this->pixels = NEW(MemoryFile(arrayPitch * (count ? count : 1)));
	}
	catch(Exception* exception)
	{
		THROW_SECONDARY("Can't create raw texture data", exception);
	}
}

PixelFormat RawTextureData::GetFormat() const
{
	return format;
}

int RawTextureData::GetImageWidth() const
{
	return width;
}

int RawTextureData::GetImageHeight() const
{
	return height;
}

int RawTextureData::GetImageDepth() const
{
	return depth;
}

int RawTextureData::GetImageSize() const
{
	return arrayPitch;
}

int RawTextureData::GetImageMips() const
{
	return mips;
}

int RawTextureData::GetCount() const
{
	return count;
}

int RawTextureData::GetMipWidth(int mip) const
{
	return std::max(width >> mip, 1);
}

int RawTextureData::GetMipHeight(int mip) const
{
	return std::max(height >> mip, 1);
}

int RawTextureData::GetMipDepth(int mip) const
{
	return std::max(depth >> mip, 1);
}

int RawTextureData::GetMipBufferWidth(int mip) const
{
	int width = GetMipWidth(mip);
	if(format.type == PixelFormat::typeCompressed)
		return (width + 3) & ~3;
	return width;
}

int RawTextureData::GetMipBufferHeight(int mip) const
{
	int height = GetMipHeight(mip);
	if(format.type == PixelFormat::typeCompressed)
		return (height + 3) & ~3;
	return height;
}

int RawTextureData::GetPixelSize() const
{
	int pixelSize;
	switch(format.type)
	{
	case PixelFormat::typeUncompressed:
		pixelSize = PixelFormat::GetPixelSize(format.size);
		break;
	default:
		pixelSize = 0;
	}
	return pixelSize;
}

void* RawTextureData::GetMipData(int image, int mip) const
{
	return (char*)pixels->GetData() + image * arrayPitch + mipOffsets[mip];
}

int RawTextureData::GetMipSize(int mip) const
{
	return GetMipSlicePitch(mip) * GetMipDepth(mip);
}

int RawTextureData::GetMipLinePitch(int mip) const
{
	int width = GetMipBufferWidth(mip);
	switch(format.type)
	{
	case PixelFormat::typeUncompressed:
		return width * PixelFormat::GetPixelSize(format.size);
	case PixelFormat::typeCompressed:
		{
			int blockSize = 0;
			switch(format.compression)
			{
			case PixelFormat::compressionBc1:
			case PixelFormat::compressionBc1Alpha:
				blockSize = 8;
				break;
			case PixelFormat::compressionBc2:
				blockSize = 16;
				break;
			case PixelFormat::compressionBc3:
				blockSize = 16;
				break;
			case PixelFormat::compressionBc4:
			case PixelFormat::compressionBc4Signed:
				blockSize = 8;
				break;
			case PixelFormat::compressionBc5:
			case PixelFormat::compressionBc5Signed:
				blockSize = 16;
				break;
			default:
				return 0;
			}
			return width / 4 * blockSize;
		}
	default:
		return 0;
	}
}

int RawTextureData::GetMipSlicePitch(int mip) const
{
	int height = GetMipBufferHeight(mip);
	if(format.type == PixelFormat::typeCompressed)
		height /= 4;
	return GetMipLinePitch(mip) * height;
}

void RawTextureData::Serialize(ptr<OutputStream> stream)
{
	try
	{
		StreamWriter writer(stream);

		format.Serialize(writer);
		writer.WriteShortly(width);
		writer.WriteShortly(height);
		writer.WriteShortly(depth);
		writer.WriteShortly(mips);
		writer.WriteShortly(count);

		int realCount = count > 0 ? count : 1;
		int realMips = mips > 0 ? mips : 1;

		for(int image = 0; image < realCount; ++image)
			for(int mip = 0; mip < realMips; ++mip)
			{
				const char* sliceData = (const char*)GetMipData(image, mip);
				int mipDepth = GetMipDepth(mip);
				int mipHeight = GetMipHeight(mip);
				int mipLinePitch = GetMipLinePitch(mip);
				int mipSlicePitch = GetMipSlicePitch(mip);
				for(int slice = 0; slice < mipDepth; ++slice)
				{
					const char* lineData = sliceData;
					for(int line = 0; line < mipHeight; ++line)
					{
						writer.Write(lineData, mipLinePitch);
						lineData += mipLinePitch;
					}
					sliceData += mipSlicePitch;
				}
			}
	}
	catch(Exception* exception)
	{
		THROW_SECONDARY("Can't serialize raw texture data", exception);
	}
}

ptr<RawTextureData> RawTextureData::Deserialize(ptr<InputStream> stream)
{
	try
	{
		StreamReader reader(stream);

		PixelFormat format = PixelFormat::Deserialize(reader);
		int width = (int)reader.ReadShortly();
		int height = (int)reader.ReadShortly();
		int depth = (int)reader.ReadShortly();
		int mips = (int)reader.ReadShortly();
		int count = (int)reader.ReadShortly();

		ptr<RawTextureData> data = NEW(RawTextureData(0, format, width, height, depth, mips, count));

		int realCount = count > 0 ? count : 1;
		int realMips = mips > 0 ? mips : 1;

		for(int image = 0; image < realCount; ++image)
			for(int mip = 0; mip < realMips; ++mip)
			{
				char* sliceData = (char*)data->GetMipData(image, mip);
				int mipDepth = data->GetMipDepth(mip);
				int mipHeight = data->GetMipHeight(mip);
				int mipLinePitch = data->GetMipLinePitch(mip);
				int mipSlicePitch = data->GetMipSlicePitch(mip);
				for(int slice = 0; slice < mipDepth; ++slice)
				{
					char* lineData = sliceData;
					for(int line = 0; line < mipHeight; ++line)
					{
						reader.Read(lineData, mipLinePitch);
						lineData += mipLinePitch;
					}
					sliceData += mipSlicePitch;
				}
			}

		return data;
	}
	catch(Exception* exception)
	{
		THROW_SECONDARY("Can't deserialize raw texture data", exception);
	}
}

void RawTextureData::Blit(ptr<RawTextureData> image, int destX, int destY, int sourceX, int sourceY, int width, int height)
{
	BEGIN_TRY();

	if(!(format == image->format))
		THROW("Format should be the same");
	if(!(
		width > 0 &&
		height > 0 &&
		depth == 0))
		THROW("Wrong destination image type");
	if(!(
		image->width > 0 &&
		image->height > 0 &&
		image->depth == 0))
		THROW("Wrong source image type");

	if(destX < 0)
	{
		sourceX -= destX;
		width += destX;
		destX = 0;
	}
	if(destY < 0)
	{
		sourceY -= destY;
		height += destY;
		destY = 0;
	}

	width = std::min(sourceX + width, image->width) - sourceX;
	width = std::min(destX + width, this->width) - destX;
	if(width <= 0)
		return;

	height = std::min(sourceY + height, image->height) - sourceY;
	height = std::min(destY + height, this->height) - destY;
	if(height <= 0)
		return;

	char* destData = (char*)GetMipData();
	char* sourceData = (char*)image->GetMipData();
	int destPitch = GetMipLinePitch();
	int sourcePitch = image->GetMipLinePitch();
	int pixelSize = GetPixelSize();
	int copySize = width * pixelSize;

	for(int i = 0; i < height; ++i)
		memcpy(
			destData + (i + destY) * destPitch + destX * pixelSize,
			sourceData + (i + sourceY) * sourcePitch + sourceX * pixelSize,
			copySize);

	END_TRY("Can't blit raw texture data");
}

ptr<RawTextureData> RawTextureData::ShelfUnion(
	const std::vector<ptr<RawTextureData> >& images,
	std::vector<std::pair<int, int> >& outPositions,
	int resultWidth,
	int border,
	bool heightPowerOfTwo
)
{
	BEGIN_TRY();

	int imagesCount = (int)images.size();

	// check types of images
	for(int i = 0; i < imagesCount; ++i)
	{
		RawTextureData* image = images[i];
		if(!(
			image->width > 0 &&
			image->height > 0 &&
			image->depth == 0 &&
			image->mips == 1 &&
			image->count == 0))
			THROW("Wrong image type");
	}

	// first pass: sort images by height
	struct Sorter
	{
		bool operator()(const std::pair<RawTextureData*, int>& a, const std::pair<RawTextureData*, int>& b) const
		{
			return a.first->GetImageHeight() > b.first->GetImageHeight();
		}
	};
	std::vector<std::pair<RawTextureData*, int> > sortedImages(imagesCount);
	for(int i = 0; i < imagesCount; ++i)
		sortedImages[i] = std::pair<RawTextureData*, size_t>(images[i], i);
	std::sort(sortedImages.begin(), sortedImages.end(), Sorter());

	// second pass: determine result height
	int resultHeight = border;
	{
		int currentX = border, currentRowHeight = 0;
		for(int i = 0; i < imagesCount; ++i)
		{
			RawTextureData* image = sortedImages[i].first;
			int width = image->GetMipWidth();
			if(currentX + width + border > resultWidth)
			{
				// move to the next row
				resultHeight += currentRowHeight;
				currentRowHeight = 0;
				currentX = border;
			}
			currentX += width + border;
			currentRowHeight = std::max(currentRowHeight, image->GetMipHeight() + border);
		}
		resultHeight += currentRowHeight;
	}

	if(heightPowerOfTwo)
	{
		int h;
		for(h = 1; h < resultHeight; h <<= 1);
		resultHeight = h;
	}

	// third pass: combine images
	outPositions.resize(imagesCount);
	ptr<MemoryFile> pixelsFile = NEW(MemoryFile(resultWidth * resultHeight));
	char* pixelsData = (char*)pixelsFile->GetData();
	memset(pixelsData, 0, pixelsFile->GetSize());
	{
		int currentX = border, currentRowY = border, currentRowHeight = 0;
		for(int i = 0; i < imagesCount; ++i)
		{
			RawTextureData* image = sortedImages[i].first;
			int width = image->GetMipWidth();
			if(currentX + width + border > resultWidth)
			{
				// move to the next row
				currentRowY += currentRowHeight;
				currentRowHeight = 0;
				currentX = border;
			}

			// place image
			int height = image->GetMipHeight();
			const char* data = (const char*)image->GetMipData();
			for(int y = 0; y < height; ++y)
				memcpy(
					pixelsData + (currentRowY + y) * resultWidth + currentX,
					data + y * width,
					width);

			// output a position
			outPositions[sortedImages[i].second].first = currentX;
			outPositions[sortedImages[i].second].second = currentRowY;

			currentX += width + border;
			currentRowHeight = std::max(currentRowHeight, height + border);
		}
	}

	return NEW(RawTextureData(
		pixelsFile,
		PixelFormat(
			PixelFormat::pixelR,
			PixelFormat::formatUint,
			PixelFormat::size8bit),
		resultWidth,
		resultHeight,
		0, // depth
		1, // mips
		0 // count
		));

	END_TRY("Can't union 2D raw images");
}

END_INANITY_GRAPHICS
<commit_msg>use BEGIN_TRY/END_TRY in RawTextureData<commit_after>#include "RawTextureData.hpp"
#include "../StreamWriter.hpp"
#include "../StreamReader.hpp"
#include "../MemoryFile.hpp"
#include "../Exception.hpp"
#include <algorithm>

BEGIN_INANITY_GRAPHICS

RawTextureData::RawTextureData(ptr<File> pixels, PixelFormat format, int width, int height, int depth, int mips, int count)
: pixels(pixels), format(format), width(width), height(height), depth(depth), mips(mips), count(count)
{
	BEGIN_TRY();

	// check params
	if(width <= 0)
		THROW("Width should be > 0");
	if(height < 0)
		THROW("Height should be >= 0");
	if(height == 0)
	{
		if(depth != 0)
			THROW("Depth != 0, but height == 0");
	}
	else
	{
		if(depth < 0)
			THROW("Depth should be >= 0");
	}
	if(mips <= 0)
		THROW("Mips should be > 0");
	if(count < 0)
		THROW("Count should be >= 0");

	// get a maximum dimension of image
	int maxDimension = std::max(std::max(width, height), depth);

	// calculate mip offsets
	// we use a floor-scheme, as it used in OpenGL
	mipOffsets.resize(mips);
	int mipOffset = 0;
	for(int i = 0; i < mips; ++i)
	{
		// if we reach the zero dimension, mips was too big
		if(maxDimension == 0)
			THROW("Too much mips");

		// store current offset
		mipOffsets[i] = mipOffset;

		// calculate size of the current mip
		int mipSize = GetMipSize(i);
		// advance offset by current mip size
		mipOffset += mipSize;

		// max dimension of next mip
		maxDimension /= 2;
	}

	// store the size of one image
	arrayPitch = mipOffset;

	// check the size of file (or create it if doesn't created yet)
	if(pixels)
	{
		if((int)pixels->GetSize() != arrayPitch * (count ? count : 1))
			THROW("Wrong file size");
	}
	else
		this->pixels = NEW(MemoryFile(arrayPitch * (count ? count : 1)));

	END_TRY("Can't create raw texture data");
}

PixelFormat RawTextureData::GetFormat() const
{
	return format;
}

int RawTextureData::GetImageWidth() const
{
	return width;
}

int RawTextureData::GetImageHeight() const
{
	return height;
}

int RawTextureData::GetImageDepth() const
{
	return depth;
}

int RawTextureData::GetImageSize() const
{
	return arrayPitch;
}

int RawTextureData::GetImageMips() const
{
	return mips;
}

int RawTextureData::GetCount() const
{
	return count;
}

int RawTextureData::GetMipWidth(int mip) const
{
	return std::max(width >> mip, 1);
}

int RawTextureData::GetMipHeight(int mip) const
{
	return std::max(height >> mip, 1);
}

int RawTextureData::GetMipDepth(int mip) const
{
	return std::max(depth >> mip, 1);
}

int RawTextureData::GetMipBufferWidth(int mip) const
{
	int width = GetMipWidth(mip);
	if(format.type == PixelFormat::typeCompressed)
		return (width + 3) & ~3;
	return width;
}

int RawTextureData::GetMipBufferHeight(int mip) const
{
	int height = GetMipHeight(mip);
	if(format.type == PixelFormat::typeCompressed)
		return (height + 3) & ~3;
	return height;
}

int RawTextureData::GetPixelSize() const
{
	int pixelSize;
	switch(format.type)
	{
	case PixelFormat::typeUncompressed:
		pixelSize = PixelFormat::GetPixelSize(format.size);
		break;
	default:
		pixelSize = 0;
	}
	return pixelSize;
}

void* RawTextureData::GetMipData(int image, int mip) const
{
	return (char*)pixels->GetData() + image * arrayPitch + mipOffsets[mip];
}

int RawTextureData::GetMipSize(int mip) const
{
	return GetMipSlicePitch(mip) * GetMipDepth(mip);
}

int RawTextureData::GetMipLinePitch(int mip) const
{
	int width = GetMipBufferWidth(mip);
	switch(format.type)
	{
	case PixelFormat::typeUncompressed:
		return width * PixelFormat::GetPixelSize(format.size);
	case PixelFormat::typeCompressed:
		{
			int blockSize = 0;
			switch(format.compression)
			{
			case PixelFormat::compressionBc1:
			case PixelFormat::compressionBc1Alpha:
				blockSize = 8;
				break;
			case PixelFormat::compressionBc2:
				blockSize = 16;
				break;
			case PixelFormat::compressionBc3:
				blockSize = 16;
				break;
			case PixelFormat::compressionBc4:
			case PixelFormat::compressionBc4Signed:
				blockSize = 8;
				break;
			case PixelFormat::compressionBc5:
			case PixelFormat::compressionBc5Signed:
				blockSize = 16;
				break;
			default:
				return 0;
			}
			return width / 4 * blockSize;
		}
	default:
		return 0;
	}
}

int RawTextureData::GetMipSlicePitch(int mip) const
{
	int height = GetMipBufferHeight(mip);
	if(format.type == PixelFormat::typeCompressed)
		height /= 4;
	return GetMipLinePitch(mip) * height;
}

void RawTextureData::Serialize(ptr<OutputStream> stream)
{
	BEGIN_TRY();

	StreamWriter writer(stream);

	format.Serialize(writer);
	writer.WriteShortly(width);
	writer.WriteShortly(height);
	writer.WriteShortly(depth);
	writer.WriteShortly(mips);
	writer.WriteShortly(count);

	int realCount = count > 0 ? count : 1;

	for(int image = 0; image < realCount; ++image)
		for(int mip = 0; mip < mips; ++mip)
		{
			const char* sliceData = (const char*)GetMipData(image, mip);
			int mipDepth = GetMipDepth(mip);
			int mipHeight = GetMipHeight(mip);
			int mipLinePitch = GetMipLinePitch(mip);
			int mipSlicePitch = GetMipSlicePitch(mip);
			for(int slice = 0; slice < mipDepth; ++slice)
			{
				const char* lineData = sliceData;
				for(int line = 0; line < mipHeight; ++line)
				{
					writer.Write(lineData, mipLinePitch);
					lineData += mipLinePitch;
				}
				sliceData += mipSlicePitch;
			}
		}

	END_TRY("Can't serialize raw texture data");
}

ptr<RawTextureData> RawTextureData::Deserialize(ptr<InputStream> stream)
{
	BEGIN_TRY();

	StreamReader reader(stream);

	PixelFormat format = PixelFormat::Deserialize(reader);
	int width = (int)reader.ReadShortly();
	int height = (int)reader.ReadShortly();
	int depth = (int)reader.ReadShortly();
	int mips = (int)reader.ReadShortly();
	int count = (int)reader.ReadShortly();

	ptr<RawTextureData> data = NEW(RawTextureData(0, format, width, height, depth, mips, count));

	int realCount = count > 0 ? count : 1;

	for(int image = 0; image < realCount; ++image)
		for(int mip = 0; mip < mips; ++mip)
		{
			char* sliceData = (char*)data->GetMipData(image, mip);
			int mipDepth = data->GetMipDepth(mip);
			int mipHeight = data->GetMipHeight(mip);
			int mipLinePitch = data->GetMipLinePitch(mip);
			int mipSlicePitch = data->GetMipSlicePitch(mip);
			for(int slice = 0; slice < mipDepth; ++slice)
			{
				char* lineData = sliceData;
				for(int line = 0; line < mipHeight; ++line)
				{
					reader.Read(lineData, mipLinePitch);
					lineData += mipLinePitch;
				}
				sliceData += mipSlicePitch;
			}
		}

	return data;

	END_TRY("Can't deserialize raw texture data");
}

void RawTextureData::Blit(ptr<RawTextureData> image, int destX, int destY, int sourceX, int sourceY, int width, int height)
{
	BEGIN_TRY();

	if(!(format == image->format))
		THROW("Format should be the same");
	if(!(
		width > 0 &&
		height > 0 &&
		depth == 0))
		THROW("Wrong destination image type");
	if(!(
		image->width > 0 &&
		image->height > 0 &&
		image->depth == 0))
		THROW("Wrong source image type");

	if(destX < 0)
	{
		sourceX -= destX;
		width += destX;
		destX = 0;
	}
	if(destY < 0)
	{
		sourceY -= destY;
		height += destY;
		destY = 0;
	}

	width = std::min(sourceX + width, image->width) - sourceX;
	width = std::min(destX + width, this->width) - destX;
	if(width <= 0)
		return;

	height = std::min(sourceY + height, image->height) - sourceY;
	height = std::min(destY + height, this->height) - destY;
	if(height <= 0)
		return;

	char* destData = (char*)GetMipData();
	char* sourceData = (char*)image->GetMipData();
	int destPitch = GetMipLinePitch();
	int sourcePitch = image->GetMipLinePitch();
	int pixelSize = GetPixelSize();
	int copySize = width * pixelSize;

	for(int i = 0; i < height; ++i)
		memcpy(
			destData + (i + destY) * destPitch + destX * pixelSize,
			sourceData + (i + sourceY) * sourcePitch + sourceX * pixelSize,
			copySize);

	END_TRY("Can't blit raw texture data");
}

ptr<RawTextureData> RawTextureData::ShelfUnion(
	const std::vector<ptr<RawTextureData> >& images,
	std::vector<std::pair<int, int> >& outPositions,
	int resultWidth,
	int border,
	bool heightPowerOfTwo
)
{
	BEGIN_TRY();

	int imagesCount = (int)images.size();

	// check types of images
	for(int i = 0; i < imagesCount; ++i)
	{
		RawTextureData* image = images[i];
		if(!(
			image->width > 0 &&
			image->height > 0 &&
			image->depth == 0 &&
			image->mips == 1 &&
			image->count == 0))
			THROW("Wrong image type");
	}

	// first pass: sort images by height
	struct Sorter
	{
		bool operator()(const std::pair<RawTextureData*, int>& a, const std::pair<RawTextureData*, int>& b) const
		{
			return a.first->GetImageHeight() > b.first->GetImageHeight();
		}
	};
	std::vector<std::pair<RawTextureData*, int> > sortedImages(imagesCount);
	for(int i = 0; i < imagesCount; ++i)
		sortedImages[i] = std::pair<RawTextureData*, size_t>(images[i], i);
	std::sort(sortedImages.begin(), sortedImages.end(), Sorter());

	// second pass: determine result height
	int resultHeight = border;
	{
		int currentX = border, currentRowHeight = 0;
		for(int i = 0; i < imagesCount; ++i)
		{
			RawTextureData* image = sortedImages[i].first;
			int width = image->GetMipWidth();
			if(currentX + width + border > resultWidth)
			{
				// move to the next row
				resultHeight += currentRowHeight;
				currentRowHeight = 0;
				currentX = border;
			}
			currentX += width + border;
			currentRowHeight = std::max(currentRowHeight, image->GetMipHeight() + border);
		}
		resultHeight += currentRowHeight;
	}

	if(heightPowerOfTwo)
	{
		int h;
		for(h = 1; h < resultHeight; h <<= 1);
		resultHeight = h;
	}

	// third pass: combine images
	outPositions.resize(imagesCount);
	ptr<MemoryFile> pixelsFile = NEW(MemoryFile(resultWidth * resultHeight));
	char* pixelsData = (char*)pixelsFile->GetData();
	memset(pixelsData, 0, pixelsFile->GetSize());
	{
		int currentX = border, currentRowY = border, currentRowHeight = 0;
		for(int i = 0; i < imagesCount; ++i)
		{
			RawTextureData* image = sortedImages[i].first;
			int width = image->GetMipWidth();
			if(currentX + width + border > resultWidth)
			{
				// move to the next row
				currentRowY += currentRowHeight;
				currentRowHeight = 0;
				currentX = border;
			}

			// place image
			int height = image->GetMipHeight();
			const char* data = (const char*)image->GetMipData();
			for(int y = 0; y < height; ++y)
				memcpy(
					pixelsData + (currentRowY + y) * resultWidth + currentX,
					data + y * width,
					width);

			// output a position
			outPositions[sortedImages[i].second].first = currentX;
			outPositions[sortedImages[i].second].second = currentRowY;

			currentX += width + border;
			currentRowHeight = std::max(currentRowHeight, height + border);
		}
	}

	return NEW(RawTextureData(
		pixelsFile,
		PixelFormat(
			PixelFormat::pixelR,
			PixelFormat::formatUint,
			PixelFormat::size8bit),
		resultWidth,
		resultHeight,
		0, // depth
		1, // mips
		0 // count
		));

	END_TRY("Can't union 2D raw images");
}

END_INANITY_GRAPHICS
<|endoftext|>


<commit_before>// Copyright (c) 2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/file_watcher.h"

#include <limits>

#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/platform_thread.h"
#include "base/scoped_temp_dir.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(OS_MACOSX)
// TODO(tony): Tests are flaky on mac.  http://crbug.com/38188
#define MAYBE(name) FLAKY_ ## name
#else
#define MAYBE(name) name
#endif

namespace {

// For tests where we wait a bit to verify nothing happened
const int kWaitForEventTime = 500;

// Maximum amount of time to wait on a test.
const int kMaxTestTimeMs = 10 * 1000;

class FileWatcherTest : public testing::Test {
 public:
  // Implementation of FileWatcher on Mac requires UI loop.
  FileWatcherTest()
      : loop_(MessageLoop::TYPE_UI),
        ui_thread_(ChromeThread::UI, &loop_),
        file_thread_(ChromeThread::FILE, &loop_),
        notified_delegates_(0),
        expected_notified_delegates_(0) {
  }

  void OnTestDelegateFirstNotification() {
    notified_delegates_++;
    if (notified_delegates_ >= expected_notified_delegates_)
      MessageLoop::current()->Quit();
  }

 protected:
  virtual void SetUp() {
    temp_dir_.reset(new ScopedTempDir);
    ASSERT_TRUE(temp_dir_->CreateUniqueTempDir());
    // Make sure that not getting an event doesn't cause the whole
    // test suite to hang.
    loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
                          kMaxTestTimeMs);
  }

  FilePath test_file() {
    return temp_dir_->path().AppendASCII("FileWatcherTest");
  }

  // Write |content| to the test file. Returns true on success.
  bool WriteTestFile(const std::string& content) {
    // Logging to try and figure out why these tests are flaky on mac.
    LOG(INFO) << "WriteTestFile";
    int write_size = file_util::WriteFile(test_file(), content.c_str(),
                                          content.length());
    return write_size == static_cast<int>(content.length());
  }

  void SetExpectedNumberOfNotifiedDelegates(int n) {
    notified_delegates_ = 0;
    expected_notified_delegates_ = n;
  }

  void VerifyExpectedNumberOfNotifiedDelegates() {
    // Check that we get at least the expected number of notified delegates.
    if (expected_notified_delegates_ - notified_delegates_ > 0)
      loop_.Run();
    EXPECT_EQ(expected_notified_delegates_, notified_delegates_);
  }

  void VerifyNoExtraNotifications() {
    // Check that we get no more than the expected number of notified delegates.
    loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
                          kWaitForEventTime);
    loop_.Run();
    EXPECT_EQ(expected_notified_delegates_, notified_delegates_);
  }

  // We need this function for reliable tests on Mac OS X. FSEvents API
  // has a latency interval and can merge multiple events into one,
  // and we need a clear distinction between events triggered by test setup code
  // and test code.
  void SyncIfPOSIX() {
#if defined(OS_POSIX)
    sync();
#endif  // defined(OS_POSIX)
  }

  MessageLoop loop_;
  ChromeThread ui_thread_;
  ChromeThread file_thread_;
  scoped_ptr<ScopedTempDir> temp_dir_;

  // The number of test delegates which received their notification.
  int notified_delegates_;

  // The number of notified test delegates after which we quit the message loop.
  int expected_notified_delegates_;
};

class TestDelegate : public FileWatcher::Delegate {
 public:
  explicit TestDelegate(FileWatcherTest* test)
      : test_(test),
        got_notification_(false) {
  }

  bool got_notification() const {
    return got_notification_;
  }

  void reset() {
    got_notification_ = false;
  }

  virtual void OnFileChanged(const FilePath& path) {
    EXPECT_TRUE(ChromeThread::CurrentlyOn(ChromeThread::UI));
    if (!got_notification_)
      test_->OnTestDelegateFirstNotification();
    got_notification_ = true;
  }

 private:
  // Hold a pointer to current test fixture to inform it on first notification.
  FileWatcherTest* test_;

  // Set to true after first notification.
  bool got_notification_;
};

// Basic test: Create the file and verify that we notice.
TEST_F(FileWatcherTest, MAYBE(NewFile)) {
  FileWatcher watcher;
  TestDelegate delegate(this);
  ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

  SetExpectedNumberOfNotifiedDelegates(1);
  ASSERT_TRUE(WriteTestFile("content"));
  VerifyExpectedNumberOfNotifiedDelegates();
}

// Verify that modifying the file is caught.
TEST_F(FileWatcherTest, MAYBE(ModifiedFile)) {
  ASSERT_TRUE(WriteTestFile("content"));
  SyncIfPOSIX();

  FileWatcher watcher;
  TestDelegate delegate(this);
  ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

  // Now make sure we get notified if the file is modified.
  SetExpectedNumberOfNotifiedDelegates(1);
  ASSERT_TRUE(WriteTestFile("new content"));
  VerifyExpectedNumberOfNotifiedDelegates();
}

TEST_F(FileWatcherTest, MAYBE(DeletedFile)) {
  ASSERT_TRUE(WriteTestFile("content"));
  SyncIfPOSIX();

  FileWatcher watcher;
  TestDelegate delegate(this);
  ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

  // Now make sure we get notified if the file is deleted.
  SetExpectedNumberOfNotifiedDelegates(1);
  file_util::Delete(test_file(), false);
  VerifyExpectedNumberOfNotifiedDelegates();
}

// Verify that letting the watcher go out of scope stops notifications.
TEST_F(FileWatcherTest, MAYBE(Unregister)) {
  TestDelegate delegate(this);

  {
    FileWatcher watcher;
    ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

    // And then let it fall out of scope, clearing its watch.
  }

  // Write a file to the test dir.
  SetExpectedNumberOfNotifiedDelegates(0);
  ASSERT_TRUE(WriteTestFile("content"));
  VerifyExpectedNumberOfNotifiedDelegates();
  VerifyNoExtraNotifications();
}

namespace {
// Used by the DeleteDuringNotify test below.
// Deletes the FileWatcher when it's notified.
class Deleter : public FileWatcher::Delegate {
 public:
  Deleter(FileWatcher* watcher, MessageLoop* loop)
      : watcher_(watcher),
        loop_(loop) {
  }

  virtual void OnFileChanged(const FilePath& path) {
    watcher_.reset(NULL);
    loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
  }

  scoped_ptr<FileWatcher> watcher_;
  MessageLoop* loop_;
};
}  // anonymous namespace

// Verify that deleting a watcher during the callback doesn't crash.
TEST_F(FileWatcherTest, MAYBE(DeleteDuringNotify)) {
  FileWatcher* watcher = new FileWatcher;
  Deleter deleter(watcher, &loop_);  // Takes ownership of watcher.
  ASSERT_TRUE(watcher->Watch(test_file(), &deleter));

  ASSERT_TRUE(WriteTestFile("content"));
  loop_.Run();

  // We win if we haven't crashed yet.
  // Might as well double-check it got deleted, too.
  ASSERT_TRUE(deleter.watcher_.get() == NULL);
}

TEST_F(FileWatcherTest, MAYBE(MultipleWatchersSingleFile)) {
  FileWatcher watcher1, watcher2;
  TestDelegate delegate1(this), delegate2(this);
  ASSERT_TRUE(watcher1.Watch(test_file(), &delegate1));
  ASSERT_TRUE(watcher2.Watch(test_file(), &delegate2));

  SetExpectedNumberOfNotifiedDelegates(2);
  ASSERT_TRUE(WriteTestFile("content"));
  VerifyExpectedNumberOfNotifiedDelegates();
}

// Verify that watching a file who's parent directory doesn't exist
// fails, but doesn't asssert.
TEST_F(FileWatcherTest, NonExistentDirectory) {
  FileWatcher watcher;
  ASSERT_FALSE(watcher.Watch(test_file().AppendASCII("FileToWatch"), NULL));
}

}  // namespace
<commit_msg>Add more sync calls to try to make the FileWatcher test not flaky on OSX.<commit_after>// Copyright (c) 2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/file_watcher.h"

#include <limits>

#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/platform_thread.h"
#include "base/scoped_temp_dir.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(OS_MACOSX)
// TODO(tony): Tests are flaky on mac.  http://crbug.com/38188
#define MAYBE(name) FLAKY_ ## name
#else
#define MAYBE(name) name
#endif

namespace {

// For tests where we wait a bit to verify nothing happened
const int kWaitForEventTime = 500;

// Maximum amount of time to wait on a test.
const int kMaxTestTimeMs = 10 * 1000;

class FileWatcherTest : public testing::Test {
 public:
  // Implementation of FileWatcher on Mac requires UI loop.
  FileWatcherTest()
      : loop_(MessageLoop::TYPE_UI),
        ui_thread_(ChromeThread::UI, &loop_),
        file_thread_(ChromeThread::FILE, &loop_),
        notified_delegates_(0),
        expected_notified_delegates_(0) {
  }

  void OnTestDelegateFirstNotification() {
    notified_delegates_++;
    if (notified_delegates_ >= expected_notified_delegates_)
      MessageLoop::current()->Quit();
  }

 protected:
  virtual void SetUp() {
    temp_dir_.reset(new ScopedTempDir);
    ASSERT_TRUE(temp_dir_->CreateUniqueTempDir());
    // Make sure that not getting an event doesn't cause the whole
    // test suite to hang.
    loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
                          kMaxTestTimeMs);
  }

  FilePath test_file() {
    return temp_dir_->path().AppendASCII("FileWatcherTest");
  }

  // Write |content| to the test file. Returns true on success.
  bool WriteTestFile(const std::string& content) {
    // Logging to try and figure out why these tests are flaky on mac.
    LOG(INFO) << "WriteTestFile";
    int write_size = file_util::WriteFile(test_file(), content.c_str(),
                                          content.length());
    SyncIfPOSIX();
    return write_size == static_cast<int>(content.length());
  }

  void SetExpectedNumberOfNotifiedDelegates(int n) {
    notified_delegates_ = 0;
    expected_notified_delegates_ = n;
  }

  void VerifyExpectedNumberOfNotifiedDelegates() {
    // Check that we get at least the expected number of notified delegates.
    if (expected_notified_delegates_ - notified_delegates_ > 0)
      loop_.Run();
    EXPECT_EQ(expected_notified_delegates_, notified_delegates_);
  }

  void VerifyNoExtraNotifications() {
    // Check that we get no more than the expected number of notified delegates.
    loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
                          kWaitForEventTime);
    loop_.Run();
    EXPECT_EQ(expected_notified_delegates_, notified_delegates_);
  }

  // We need this function for reliable tests on Mac OS X. FSEvents API
  // has a latency interval and can merge multiple events into one,
  // and we need a clear distinction between events triggered by test setup code
  // and test code.
  void SyncIfPOSIX() {
#if defined(OS_POSIX)
    sync();
#endif  // defined(OS_POSIX)
  }

  MessageLoop loop_;
  ChromeThread ui_thread_;
  ChromeThread file_thread_;
  scoped_ptr<ScopedTempDir> temp_dir_;

  // The number of test delegates which received their notification.
  int notified_delegates_;

  // The number of notified test delegates after which we quit the message loop.
  int expected_notified_delegates_;
};

class TestDelegate : public FileWatcher::Delegate {
 public:
  explicit TestDelegate(FileWatcherTest* test)
      : test_(test),
        got_notification_(false) {
  }

  bool got_notification() const {
    return got_notification_;
  }

  void reset() {
    got_notification_ = false;
  }

  virtual void OnFileChanged(const FilePath& path) {
    EXPECT_TRUE(ChromeThread::CurrentlyOn(ChromeThread::UI));
    if (!got_notification_)
      test_->OnTestDelegateFirstNotification();
    got_notification_ = true;
  }

 private:
  // Hold a pointer to current test fixture to inform it on first notification.
  FileWatcherTest* test_;

  // Set to true after first notification.
  bool got_notification_;
};

// Basic test: Create the file and verify that we notice.
TEST_F(FileWatcherTest, MAYBE(NewFile)) {
  FileWatcher watcher;
  TestDelegate delegate(this);
  ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

  SetExpectedNumberOfNotifiedDelegates(1);
  ASSERT_TRUE(WriteTestFile("content"));
  VerifyExpectedNumberOfNotifiedDelegates();
}

// Verify that modifying the file is caught.
TEST_F(FileWatcherTest, MAYBE(ModifiedFile)) {
  ASSERT_TRUE(WriteTestFile("content"));

  FileWatcher watcher;
  TestDelegate delegate(this);
  ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

  // Now make sure we get notified if the file is modified.
  SetExpectedNumberOfNotifiedDelegates(1);
  ASSERT_TRUE(WriteTestFile("new content"));
  VerifyExpectedNumberOfNotifiedDelegates();
}

TEST_F(FileWatcherTest, MAYBE(DeletedFile)) {
  ASSERT_TRUE(WriteTestFile("content"));

  FileWatcher watcher;
  TestDelegate delegate(this);
  ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

  // Now make sure we get notified if the file is deleted.
  SetExpectedNumberOfNotifiedDelegates(1);
  file_util::Delete(test_file(), false);
  SyncIfPOSIX();
  VerifyExpectedNumberOfNotifiedDelegates();
}

// Verify that letting the watcher go out of scope stops notifications.
TEST_F(FileWatcherTest, MAYBE(Unregister)) {
  TestDelegate delegate(this);

  {
    FileWatcher watcher;
    ASSERT_TRUE(watcher.Watch(test_file(), &delegate));

    // And then let it fall out of scope, clearing its watch.
  }

  // Write a file to the test dir.
  SetExpectedNumberOfNotifiedDelegates(0);
  ASSERT_TRUE(WriteTestFile("content"));
  VerifyExpectedNumberOfNotifiedDelegates();
  VerifyNoExtraNotifications();
}

namespace {
// Used by the DeleteDuringNotify test below.
// Deletes the FileWatcher when it's notified.
class Deleter : public FileWatcher::Delegate {
 public:
  Deleter(FileWatcher* watcher, MessageLoop* loop)
      : watcher_(watcher),
        loop_(loop) {
  }

  virtual void OnFileChanged(const FilePath& path) {
    watcher_.reset(NULL);
    loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
  }

  scoped_ptr<FileWatcher> watcher_;
  MessageLoop* loop_;
};
}  // anonymous namespace

// Verify that deleting a watcher during the callback doesn't crash.
TEST_F(FileWatcherTest, MAYBE(DeleteDuringNotify)) {
  FileWatcher* watcher = new FileWatcher;
  Deleter deleter(watcher, &loop_);  // Takes ownership of watcher.
  ASSERT_TRUE(watcher->Watch(test_file(), &deleter));

  ASSERT_TRUE(WriteTestFile("content"));
  loop_.Run();

  // We win if we haven't crashed yet.
  // Might as well double-check it got deleted, too.
  ASSERT_TRUE(deleter.watcher_.get() == NULL);
}

TEST_F(FileWatcherTest, MAYBE(MultipleWatchersSingleFile)) {
  FileWatcher watcher1, watcher2;
  TestDelegate delegate1(this), delegate2(this);
  ASSERT_TRUE(watcher1.Watch(test_file(), &delegate1));
  ASSERT_TRUE(watcher2.Watch(test_file(), &delegate2));

  SetExpectedNumberOfNotifiedDelegates(2);
  ASSERT_TRUE(WriteTestFile("content"));
  VerifyExpectedNumberOfNotifiedDelegates();
}

// Verify that watching a file who's parent directory doesn't exist
// fails, but doesn't asssert.
TEST_F(FileWatcherTest, NonExistentDirectory) {
  FileWatcher watcher;
  ASSERT_FALSE(watcher.Watch(test_file().AppendASCII("FileToWatch"), NULL));
}

}  // namespace
<|endoftext|>


<commit_before><commit_msg>DevTools: Provide nice initial location/size for the devtools window.<commit_after><|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/sync/notification_method.h"

#include "base/logging.h"

namespace browser_sync {

// TODO(akalin): Eventually change this to NOTIFICATION_NEW.
const NotificationMethod kDefaultNotificationMethod =
    NOTIFICATION_TRANSITIONAL;

std::string NotificationMethodToString(
    NotificationMethod notification_method) {
  switch (notification_method) {
    case NOTIFICATION_LEGACY:
      return "NOTIFICATION_LEGACY";
      break;
    case NOTIFICATION_TRANSITIONAL:
      return "NOTIFICATION_TRANSITIONAL";
      break;
    case NOTIFICATION_NEW:
      return "NOTIFICATION_NEW";
      break;
    case NOTIFICATION_SERVER:
      return "NOTIFICATION_SERVER";
      break;
    default:
      LOG(WARNING) << "Unknown value for notification method: "
                   << notification_method;
      break;
  }
  return "<unknown notification method>";
}

NotificationMethod StringToNotificationMethod(const std::string& str) {
  if (str == "legacy") {
    return NOTIFICATION_LEGACY;
  } else if (str == "transitional") {
    return NOTIFICATION_TRANSITIONAL;
  } else if (str == "new") {
    return NOTIFICATION_NEW;
  } else if (str == "server") {
    return NOTIFICATION_SERVER;
  }
  LOG(WARNING) << "Unknown notification method \"" << str
               << "\"; using method "
               << NotificationMethodToString(kDefaultNotificationMethod);
  return kDefaultNotificationMethod;
}

}  // namespace browser_sync
<commit_msg>Set default sync notification method to "server".<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/sync/notification_method.h"

#include "base/logging.h"

namespace browser_sync {

const NotificationMethod kDefaultNotificationMethod =
    NOTIFICATION_SERVER;

std::string NotificationMethodToString(
    NotificationMethod notification_method) {
  switch (notification_method) {
    case NOTIFICATION_LEGACY:
      return "NOTIFICATION_LEGACY";
      break;
    case NOTIFICATION_TRANSITIONAL:
      return "NOTIFICATION_TRANSITIONAL";
      break;
    case NOTIFICATION_NEW:
      return "NOTIFICATION_NEW";
      break;
    case NOTIFICATION_SERVER:
      return "NOTIFICATION_SERVER";
      break;
    default:
      LOG(WARNING) << "Unknown value for notification method: "
                   << notification_method;
      break;
  }
  return "<unknown notification method>";
}

NotificationMethod StringToNotificationMethod(const std::string& str) {
  if (str == "legacy") {
    return NOTIFICATION_LEGACY;
  } else if (str == "transitional") {
    return NOTIFICATION_TRANSITIONAL;
  } else if (str == "new") {
    return NOTIFICATION_NEW;
  } else if (str == "server") {
    return NOTIFICATION_SERVER;
  }
  LOG(WARNING) << "Unknown notification method \"" << str
               << "\"; using method "
               << NotificationMethodToString(kDefaultNotificationMethod);
  return kDefaultNotificationMethod;
}

}  // namespace browser_sync
<|endoftext|>


<commit_before>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <shlwapi.h>
#include <sstream>
#include <string>

#include "base/command_line.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/platform_thread.h"
#include "base/string_util.h"
#include "chrome/browser/automation/url_request_mock_http_job.h"
#include "chrome/browser/automation/url_request_slow_download_job.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/ui/ui_test.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/automation/browser_proxy.h"
#include "net/url_request/url_request_unittest.h"

using std::wstring;

namespace {

const wchar_t kDocRoot[] = L"chrome/test/data";

// Checks if the volume supports Alternate Data Streams. This is required for
// the Zone Identifier implementation.
bool VolumeSupportsADS(const std::wstring path) {
  wchar_t drive[MAX_PATH] = {0};
  wcscpy_s(drive, MAX_PATH, path.c_str());

  EXPECT_TRUE(PathStripToRootW(drive));

  DWORD fs_flags = 0;
  EXPECT_TRUE(GetVolumeInformationW(drive, NULL, 0, 0, NULL, &fs_flags, NULL,
                                    0));

  if (fs_flags & FILE_NAMED_STREAMS)
    return true;

  return false;
}

// Checks if the ZoneIdentifier is correctly set to "Internet" (3)
void CheckZoneIdentifier(const std::wstring full_path) {
  const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;

  std::wstring path = full_path + L":Zone.Identifier";
  HANDLE file = CreateFile(path.c_str(), GENERIC_READ, kShare, NULL,
                           OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  ASSERT_TRUE(INVALID_HANDLE_VALUE != file);

  char buffer[100] = {0};
  DWORD read = 0;
  ASSERT_TRUE(ReadFile(file, buffer, 100, &read, NULL));
  CloseHandle(file);

  const char kIdentifier[] = "[ZoneTransfer]\nZoneId=3";
  ASSERT_EQ(arraysize(kIdentifier), read);

  ASSERT_EQ(0, strcmp(kIdentifier, buffer));
}

class DownloadTest : public UITest {
 protected:
  DownloadTest() : UITest() {}

  void CleanUpDownload(const std::wstring& client_filename,
                       const std::wstring& server_filename) {
    // Find the path on the client.
    std::wstring file_on_client(download_prefix_);
    file_on_client.append(client_filename);
    EXPECT_TRUE(file_util::PathExists(file_on_client));

    // Find the path on the server.
    std::wstring file_on_server;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA,
                                 &file_on_server));
    file_on_server.append(L"\\");
    file_on_server.append(server_filename);
    ASSERT_TRUE(file_util::PathExists(file_on_server));

    // Check that we downloaded the file correctly.
    EXPECT_TRUE(file_util::ContentsEqual(file_on_server,
                                         file_on_client));

    // Check if the Zone Identifier is correclty set.
    if (VolumeSupportsADS(file_on_client))
      CheckZoneIdentifier(file_on_client);

    // Delete the client copy of the file.
    EXPECT_TRUE(file_util::Delete(file_on_client, false));
  }

  void CleanUpDownload(const std::wstring& file) {
    CleanUpDownload(file, file);
  }

  virtual void SetUp() {
    UITest::SetUp();
    download_prefix_ = GetDownloadDirectory();
    download_prefix_ += FilePath::kSeparators[0];
  }

 protected:
  void RunSizeTest(const wstring& url,
                   const wstring& expected_title_in_progress,
                   const wstring& expected_title_finished) {
    {
      EXPECT_EQ(1, GetTabCount());

      NavigateToURL(GURL(url));
      // Downloads appear in the shelf
      WaitUntilTabCount(1);
      // TODO(tc): check download status text

      // Complete sending the request.  We do this by loading a second URL in a
      // separate tab.
      scoped_ptr<BrowserProxy> window(automation()->GetBrowserWindow(0));
      EXPECT_TRUE(window->AppendTab(GURL(
          URLRequestSlowDownloadJob::kFinishDownloadUrl)));
      EXPECT_EQ(2, GetTabCount());
      // TODO(tc): check download status text

      // Make sure the download shelf is showing.
      scoped_ptr<TabProxy> dl_tab(window->GetTab(0));
      ASSERT_TRUE(dl_tab.get());
      EXPECT_TRUE(WaitForDownloadShelfVisible(dl_tab.get()));
    }

    std::wstring filename = file_util::GetFilenameFromPath(url);
    EXPECT_TRUE(file_util::PathExists(download_prefix_ + filename));

    // Delete the file we just downloaded.
    for (int i = 0; i < 10; ++i) {
      if (file_util::Delete(download_prefix_ + filename, false))
        break;
      PlatformThread::Sleep(action_max_timeout_ms() / 10);
    }
    EXPECT_FALSE(file_util::PathExists(download_prefix_ + filename));
  }

  wstring download_prefix_;
};

}  // namespace

// Download a file with non-viewable content, verify that the
// download tab opened and the file exists.
TEST_F(DownloadTest, DownloadMimeType) {
  wstring file = L"download-test1.lib";
  wstring expected_title = L"100% - " + file;

  EXPECT_EQ(1, GetTabCount());

  NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file));
  // No new tabs created, downloads appear in the current tab's download shelf.
  WaitUntilTabCount(1);

  // Wait until the file is downloaded.
  PlatformThread::Sleep(action_timeout_ms());

  CleanUpDownload(file);

  scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
  ASSERT_TRUE(tab_proxy.get());
  EXPECT_TRUE(WaitForDownloadShelfVisible(tab_proxy.get()));
}

// Access a file with a viewable mime-type, verify that a download
// did not initiate.
TEST_F(DownloadTest, NoDownload) {
  wstring file = L"download-test2.html";
  wstring file_path = download_prefix_;
  file_util::AppendToPath(&file_path, file);

  if (file_util::PathExists(file_path))
    ASSERT_TRUE(file_util::Delete(file_path, false));

  EXPECT_EQ(1, GetTabCount());

  NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file));
  WaitUntilTabCount(1);

  // Wait to see if the file will be downloaded.
  PlatformThread::Sleep(action_timeout_ms());

  EXPECT_FALSE(file_util::PathExists(file_path));
  if (file_util::PathExists(file_path))
    ASSERT_TRUE(file_util::Delete(file_path, false));

  scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
  ASSERT_TRUE(tab_proxy.get());
  EXPECT_FALSE(WaitForDownloadShelfVisible(tab_proxy.get()));
}

// Download a 0-size file with a content-disposition header, verify that the
// download tab opened and the file exists as the filename specified in the
// header.  This also ensures we properly handle empty file downloads.
TEST_F(DownloadTest, ContentDisposition) {
  wstring file = L"download-test3.gif";
  wstring download_file = L"download-test3-attachment.gif";
  wstring expected_title = L"100% - " + download_file;

  EXPECT_EQ(1, GetTabCount());

  NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file));
  WaitUntilTabCount(1);

  // Wait until the file is downloaded.
  PlatformThread::Sleep(action_timeout_ms());

  CleanUpDownload(download_file, file);

  // Ensure the download shelf is visible on the current tab.
  scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
  ASSERT_TRUE(tab_proxy.get());
  EXPECT_TRUE(WaitForDownloadShelfVisible(tab_proxy.get()));
}

// UnknownSize and KnownSize are tests which depend on
// URLRequestSlowDownloadJob to serve content in a certain way. Data will be
// sent in two chunks where the first chunk is 35K and the second chunk is 10K.
// The test will first attempt to download a file; but the server will "pause"
// in the middle until the server receives a second request for
// "download-finish.  At that time, the download will finish.
TEST_F(DownloadTest, UnknownSize) {
  std::wstring url(URLRequestSlowDownloadJob::kUnknownSizeUrl);
  std::wstring filename = file_util::GetFilenameFromPath(url);
  RunSizeTest(url, L"32.0 KB - " + filename, L"100% - " + filename);
}

// http://b/1158253
TEST_F(DownloadTest, DISABLED_KnownSize) {
  std::wstring url(URLRequestSlowDownloadJob::kKnownSizeUrl);
  std::wstring filename = file_util::GetFilenameFromPath(url);
  RunSizeTest(url, L"71% - " + filename, L"100% - " + filename);
}
<commit_msg>Disabling a couple of UI tests while I investigate.<commit_after>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <shlwapi.h>
#include <sstream>
#include <string>

#include "base/command_line.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/platform_thread.h"
#include "base/string_util.h"
#include "chrome/browser/automation/url_request_mock_http_job.h"
#include "chrome/browser/automation/url_request_slow_download_job.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/ui/ui_test.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/automation/browser_proxy.h"
#include "net/url_request/url_request_unittest.h"

using std::wstring;

namespace {

const wchar_t kDocRoot[] = L"chrome/test/data";

// Checks if the volume supports Alternate Data Streams. This is required for
// the Zone Identifier implementation.
bool VolumeSupportsADS(const std::wstring path) {
  wchar_t drive[MAX_PATH] = {0};
  wcscpy_s(drive, MAX_PATH, path.c_str());

  EXPECT_TRUE(PathStripToRootW(drive));

  DWORD fs_flags = 0;
  EXPECT_TRUE(GetVolumeInformationW(drive, NULL, 0, 0, NULL, &fs_flags, NULL,
                                    0));

  if (fs_flags & FILE_NAMED_STREAMS)
    return true;

  return false;
}

// Checks if the ZoneIdentifier is correctly set to "Internet" (3)
void CheckZoneIdentifier(const std::wstring full_path) {
  const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;

  std::wstring path = full_path + L":Zone.Identifier";
  HANDLE file = CreateFile(path.c_str(), GENERIC_READ, kShare, NULL,
                           OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  ASSERT_TRUE(INVALID_HANDLE_VALUE != file);

  char buffer[100] = {0};
  DWORD read = 0;
  ASSERT_TRUE(ReadFile(file, buffer, 100, &read, NULL));
  CloseHandle(file);

  const char kIdentifier[] = "[ZoneTransfer]\nZoneId=3";
  ASSERT_EQ(arraysize(kIdentifier), read);

  ASSERT_EQ(0, strcmp(kIdentifier, buffer));
}

class DownloadTest : public UITest {
 protected:
  DownloadTest() : UITest() {}

  void CleanUpDownload(const std::wstring& client_filename,
                       const std::wstring& server_filename) {
    // Find the path on the client.
    std::wstring file_on_client(download_prefix_);
    file_on_client.append(client_filename);
    EXPECT_TRUE(file_util::PathExists(file_on_client));

    // Find the path on the server.
    std::wstring file_on_server;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA,
                                 &file_on_server));
    file_on_server.append(L"\\");
    file_on_server.append(server_filename);
    ASSERT_TRUE(file_util::PathExists(file_on_server));

    // Check that we downloaded the file correctly.
    EXPECT_TRUE(file_util::ContentsEqual(file_on_server,
                                         file_on_client));

    // Check if the Zone Identifier is correclty set.
    if (VolumeSupportsADS(file_on_client))
      CheckZoneIdentifier(file_on_client);

    // Delete the client copy of the file.
    EXPECT_TRUE(file_util::Delete(file_on_client, false));
  }

  void CleanUpDownload(const std::wstring& file) {
    CleanUpDownload(file, file);
  }

  virtual void SetUp() {
    UITest::SetUp();
    download_prefix_ = GetDownloadDirectory();
    download_prefix_ += FilePath::kSeparators[0];
  }

 protected:
  void RunSizeTest(const wstring& url,
                   const wstring& expected_title_in_progress,
                   const wstring& expected_title_finished) {
    {
      EXPECT_EQ(1, GetTabCount());

      NavigateToURL(GURL(url));
      // Downloads appear in the shelf
      WaitUntilTabCount(1);
      // TODO(tc): check download status text

      // Complete sending the request.  We do this by loading a second URL in a
      // separate tab.
      scoped_ptr<BrowserProxy> window(automation()->GetBrowserWindow(0));
      EXPECT_TRUE(window->AppendTab(GURL(
          URLRequestSlowDownloadJob::kFinishDownloadUrl)));
      EXPECT_EQ(2, GetTabCount());
      // TODO(tc): check download status text

      // Make sure the download shelf is showing.
      scoped_ptr<TabProxy> dl_tab(window->GetTab(0));
      ASSERT_TRUE(dl_tab.get());
      EXPECT_TRUE(WaitForDownloadShelfVisible(dl_tab.get()));
    }

    std::wstring filename = file_util::GetFilenameFromPath(url);
    EXPECT_TRUE(file_util::PathExists(download_prefix_ + filename));

    // Delete the file we just downloaded.
    for (int i = 0; i < 10; ++i) {
      if (file_util::Delete(download_prefix_ + filename, false))
        break;
      PlatformThread::Sleep(action_max_timeout_ms() / 10);
    }
    EXPECT_FALSE(file_util::PathExists(download_prefix_ + filename));
  }

  wstring download_prefix_;
};

}  // namespace

// Download a file with non-viewable content, verify that the
// download tab opened and the file exists.
TEST_F(DownloadTest, DISABLED_DownloadMimeType) {
  wstring file = L"download-test1.lib";
  wstring expected_title = L"100% - " + file;

  EXPECT_EQ(1, GetTabCount());

  NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file));
  // No new tabs created, downloads appear in the current tab's download shelf.
  WaitUntilTabCount(1);

  // Wait until the file is downloaded.
  PlatformThread::Sleep(action_timeout_ms());

  CleanUpDownload(file);

  scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
  ASSERT_TRUE(tab_proxy.get());
  EXPECT_TRUE(WaitForDownloadShelfVisible(tab_proxy.get()));
}

// Access a file with a viewable mime-type, verify that a download
// did not initiate.
TEST_F(DownloadTest, NoDownload) {
  wstring file = L"download-test2.html";
  wstring file_path = download_prefix_;
  file_util::AppendToPath(&file_path, file);

  if (file_util::PathExists(file_path))
    ASSERT_TRUE(file_util::Delete(file_path, false));

  EXPECT_EQ(1, GetTabCount());

  NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file));
  WaitUntilTabCount(1);

  // Wait to see if the file will be downloaded.
  PlatformThread::Sleep(action_timeout_ms());

  EXPECT_FALSE(file_util::PathExists(file_path));
  if (file_util::PathExists(file_path))
    ASSERT_TRUE(file_util::Delete(file_path, false));

  scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
  ASSERT_TRUE(tab_proxy.get());
  EXPECT_FALSE(WaitForDownloadShelfVisible(tab_proxy.get()));
}

// Download a 0-size file with a content-disposition header, verify that the
// download tab opened and the file exists as the filename specified in the
// header.  This also ensures we properly handle empty file downloads.
TEST_F(DownloadTest, DISABLED_ContentDisposition) {
  wstring file = L"download-test3.gif";
  wstring download_file = L"download-test3-attachment.gif";
  wstring expected_title = L"100% - " + download_file;

  EXPECT_EQ(1, GetTabCount());

  NavigateToURL(URLRequestMockHTTPJob::GetMockUrl(file));
  WaitUntilTabCount(1);

  // Wait until the file is downloaded.
  PlatformThread::Sleep(action_timeout_ms());

  CleanUpDownload(download_file, file);

  // Ensure the download shelf is visible on the current tab.
  scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
  ASSERT_TRUE(tab_proxy.get());
  EXPECT_TRUE(WaitForDownloadShelfVisible(tab_proxy.get()));
}

// UnknownSize and KnownSize are tests which depend on
// URLRequestSlowDownloadJob to serve content in a certain way. Data will be
// sent in two chunks where the first chunk is 35K and the second chunk is 10K.
// The test will first attempt to download a file; but the server will "pause"
// in the middle until the server receives a second request for
// "download-finish.  At that time, the download will finish.
TEST_F(DownloadTest, UnknownSize) {
  std::wstring url(URLRequestSlowDownloadJob::kUnknownSizeUrl);
  std::wstring filename = file_util::GetFilenameFromPath(url);
  RunSizeTest(url, L"32.0 KB - " + filename, L"100% - " + filename);
}

// http://b/1158253
TEST_F(DownloadTest, DISABLED_KnownSize) {
  std::wstring url(URLRequestSlowDownloadJob::kKnownSizeUrl);
  std::wstring filename = file_util::GetFilenameFromPath(url);
  RunSizeTest(url, L"71% - " + filename, L"100% - " + filename);
}
<|endoftext|>


<commit_before>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2021.  All rights reserved
 */
#include "../StroikaPreComp.h"

#include "../../Foundation/Characters/Format.h"
#include "../../Foundation/Characters/ToString.h"
#include "../../Foundation/Execution/Exceptions.h"
#include "../../Foundation/IO/Network/HTTP/ClientErrorException.h"
#include "../../Foundation/IO/Network/HTTP/Headers.h"
#include "../../Foundation/IO/Network/HTTP/Methods.h"

#include "Router.h"

using namespace Stroika::Foundation;
using namespace Stroika::Foundation::Characters;
using namespace Stroika::Foundation::Containers;
using namespace Stroika::Foundation::Memory;
using namespace Stroika::Foundation::IO::Network;

using namespace Stroika::Frameworks;
using namespace Stroika::Frameworks::WebServer;

using HTTP::ClientErrorException;

// Comment this in to turn on aggressive noisy DbgTrace in this module
//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1

/*
 *  IMPLEMENTATION HINTS:
 *      A good place to look for a mature implementation of similar logic would be
 *          https://github.com/apache/cxf/blob/master/rt/rs/security/cors/src/main/java/org/apache/cxf/rs/security/cors/CrossOriginResourceSharingFilter.java
 */

namespace {
    String ExtractHostRelPath_ (const URI& url)
    {
        try {
            return url.GetAbsPath<String> ().SubString (1); // According to https://tools.ietf.org/html/rfc2616#section-5.1.2 - the URI must be abs_path
        }
        catch (...) {
            Execution::Throw (ClientErrorException{HTTP::StatusCodes::kBadRequest, L"request URI requires an absolute path"sv});
        }
    }
}

/*
 ********************************************************************************
 ******************************* Route::Matches *********************************
 ********************************************************************************
 */
bool Route::Matches (const Request& request, Sequence<String>* pathRegExpMatches) const
{
    return Matches (request.httpMethod (), ExtractHostRelPath_ (request.url ()), request, pathRegExpMatches);
}
bool Route::Matches (const String& method, const String& hostRelPath, const Request& request, Sequence<String>* pathRegExpMatches) const
{
    if (fVerbAndPathMatch_) {
        if (not method.Match (fVerbAndPathMatch_->first)) {
            return false;
        }
        return (pathRegExpMatches == nullptr)
                   ? hostRelPath.Match (fVerbAndPathMatch_->second)
                   : hostRelPath.Match (fVerbAndPathMatch_->second, pathRegExpMatches);
    }
    else if (fRequestMatch_) {
        return (*fRequestMatch_) (method, hostRelPath, request);
    }
    else {
        AssertNotReached ();
        return false;
    }
}

/*
 ********************************************************************************
 ************************* WebServer::Router::Rep_ ******************************
 ********************************************************************************
 */

struct Router::Rep_ : Interceptor::_IRep {
    static const optional<Set<String>> MapStartToNullOpt_ (const optional<Set<String>>& o)
    {
        // internally we treat missing as wildcard but caller may not, so map
        optional<Set<String>> m = o;
        if (m and m->Contains (CORSOptions::kAccessControlWildcard)) {
            m = nullopt;
        }
        if (m) {
            m = Set<String>{kHeaderNameEqualsComparer, *m};
        }
        return m;
    }
    // requires all optional values filled in on corsOptions
    Rep_ (const Sequence<Route>& routes, const CORSOptions& filledInCORSOptions)
        : fAllowedOrigins_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedOrigins)}
        , fAllowedHeaders_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedHeaders)}
        , fAccessControlAllowCredentialsValue_{*filledInCORSOptions.fAllowCredentials ? L"true"sv : L"false"sv}
        , fAccessControlMaxAgeValue_{Characters::Format (L"%d", *filledInCORSOptions.fAccessControlMaxAge)}
        , fRoutes_{routes}
    {
    }
    virtual void HandleMessage (Message* m) const override
    {
#if USE_NOISY_TRACE_IN_THIS_MODULE_
        Debug::TraceContextBumper ctx{L"Router::Rep_::HandleMessage", L"(...method=%s,url=%s)", m->GetRequestHTTPMethod ().c_str (), Characters::ToString (m->GetRequestURL ()).c_str ()};
#endif
        Sequence<String>         matches;
        optional<RequestHandler> handler = Lookup_ (m->request (), &matches);
        if (handler) {
            Handle_GET_ (m, matches, *handler);
        }
        else if (m->request ().httpMethod () == HTTP::Methods::kHead and Handle_HEAD_ (m)) {
            // handled
        }
        else if (m->request ().httpMethod () == HTTP::Methods::kOptions) {
            Handle_OPTIONS_ (m);
        }
        else {
            if (optional<Set<String>> o = GetAllowedMethodsForRequest_ (m->request ())) {
                // From 10.4.6 405 Method Not Allowed
                //      The method specified in the Request-Line is not allowed for the resource identified by the Request-URI.
                //      The response MUST include an Allow header containing a list of valid methods for the requested resource.
                Assert (not o->empty ());
                m->rwResponse ().UpdateHeader ([&] (auto* header) { RequireNotNull (header); header->allow = o; });
                Execution::Throw (ClientErrorException{HTTP::StatusCodes::kMethodNotAllowed});
            }
            else {
                DbgTrace (L"Router 404: (...url=%s)", Characters::ToString (m->request ().url ()).c_str ());
                Execution::Throw (ClientErrorException{HTTP::StatusCodes::kNotFound});
            }
        }
    }
    nonvirtual void HandleCORSInNormallyHandledMessage_ (const Request& request, Response& response) const
    {
        /*
         *  Origin and Access-Control-Allow-Origin
         *      documented here:    http://www.w3.org/TR/cors/#http-responses
         *
         *  IF CORS is being used, the request will contain an Origin header, and will expect a
         *  corresponding Access-Control-Allow-Origin response header. If the response header is missing
         *  that implies a CORS failure (but if we have no header in the request - presumably no matter)
         */
        optional<String> allowedOrigin;
        if (auto origin = request.headers ().origin ()) {
            if (fAllowedOrigins_.has_value ()) {
                // see https://fetch.spec.whatwg.org/#http-origin for hints about how to compare - not sure
                // may need to be more flexible about how we compare, but for now a good approximation... &&& @todo docs above link say how to compar
                String originStr = origin->ToString ();
                if (fAllowedOrigins_->Contains (originStr)) {
                    allowedOrigin = originStr;
                }
            }
            else {
                allowedOrigin = CORSOptions::kAccessControlWildcard;
            }
        }
        if (allowedOrigin) {
            response.UpdateHeader ([&] (auto* header) {
                RequireNotNull (header);
                header->accessControlAllowOrigin = allowedOrigin;
                if (fAllowedOrigins_) {
                    // see https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches to see why we need to add Vary response
                    // if response depends on origin (so not '*')
                    header->vary = Memory::NullCoalesce (header->vary ()) + String{HTTP::HeaderName::kOrigin};
                }
            });
        }
    }
    nonvirtual optional<RequestHandler> Lookup_ (const String& method, const String& hostRelPath, const Request& request, Sequence<String>* matches) const
    {
        for (Route r : fRoutes_) {
            if (r.Matches (method, hostRelPath, request, matches)) {
                return r.fHandler_;
            }
        }
        return nullopt;
    }
    nonvirtual optional<RequestHandler> Lookup_ (const Request& request, Sequence<String>* matches) const
    {
        return Lookup_ (request.httpMethod (), ExtractHostRelPath_ (request.url ()), request, matches);
    }
    nonvirtual optional<Set<String>> GetAllowedMethodsForRequest_ (const Request& request) const
    {
        String                   hostRelPath = ExtractHostRelPath_ (request.GetURL ());
        static const Set<String> kMethods2Try_{HTTP::Methods::kGet, HTTP::Methods::kPut, HTTP::Methods::kOptions, HTTP::Methods::kDelete, HTTP::Methods::kPost};
        Set<String>              methods;
        for (String method : kMethods2Try_) {
            for (Route r : fRoutes_) {
                if (r.Matches (method, hostRelPath, request)) {
                    methods.Add (method);
                }
            }
        }
        return methods.empty () ? nullopt : optional<Set<String>>{methods};
    }
    nonvirtual void Handle_GET_ (Message* message, const Sequence<String>& matches, const RequestHandler& handler) const
    {
        const Request& request  = message->request ();
        Response&      response = message->rwResponse ();
        (handler) (message, matches);
        HandleCORSInNormallyHandledMessage_ (request, response);
    }
    nonvirtual bool Handle_HEAD_ (Message* message) const
    {
        const Request&   request  = message->request ();
        Response&        response = message->rwResponse ();
        Sequence<String> matches;
        if (optional<RequestHandler> handler = Lookup_ (Methods::kGet, ExtractHostRelPath_ (request.url ()), request, &matches)) {
            // do someting to response so 'in HEAD mode' and won't write
            response.EnterHeadMode ();
            (*handler) (message, matches);
            HandleCORSInNormallyHandledMessage_ (request, response);
            return true;
        }
        return false;
    }
    nonvirtual void Handle_OPTIONS_ (Message* message) const
    {
        const Request& request  = message->request ();
        Response&      response = message->rwResponse ();
        // @todo note - This ignores - Access-Control-Request-Method - not sure how we are expected to use it?
        auto o = GetAllowedMethodsForRequest_ (request);
        if (o) {
            auto accessControlRequestHeaders = request.headers ().LookupOne (HeaderName::kAccessControlRequestHeaders);
            response.UpdateHeader ([this, &o, &accessControlRequestHeaders] (auto* header) {
                RequireNotNull (header);
                header->Set (HeaderName::kAccessControlAllowCredentials, fAccessControlAllowCredentialsValue_);
                if (accessControlRequestHeaders) {
                    if (fAllowedHeaders_) {
                        // intersect requested headers with those configured to permit
                        Iterable<String> requestAccessHeaders = accessControlRequestHeaders->Tokenize ({','});
                        auto             r                    = fAllowedHeaders_->Intersection (requestAccessHeaders);
                        if (r.empty ()) {
                            header->Set (HeaderName::kAccessControlAllowHeaders, String::Join (r));
                        }
                    }
                    else {
                        header->Set (HeaderName::kAccessControlAllowHeaders, *accessControlRequestHeaders);
                    }
                }
                header->Set (HeaderName::kAccessControlAllowMethods, String::Join (*o));
                header->Set (HeaderName::kAccessControlMaxAge, fAccessControlMaxAgeValue_);
            });
            HandleCORSInNormallyHandledMessage_ (request, response); // include access-origin-header
            response.SetStatus (IO::Network::HTTP::StatusCodes::kNoContent);
        }
        else {
            DbgTrace (L"Router 404: (...url=%s)", Characters::ToString (message->request ().url ()).c_str ());
            Execution::Throw (ClientErrorException{HTTP::StatusCodes::kNotFound});
        }
    }

    const optional<Set<String>> fAllowedOrigins_; // missing <==> '*'
    const optional<Set<String>> fAllowedHeaders_; // missing <==> '*'
    const String                fAccessControlAllowCredentialsValue_;
    const String                fAccessControlMaxAgeValue_;
    const Sequence<Route>       fRoutes_; // no need for synchronization cuz constant - just set on construction
};

/*
 ********************************************************************************
 *************************** WebServer::Router **********************************
 ********************************************************************************
 */
namespace {
    CORSOptions FillIn_ (CORSOptions corsOptions)
    {
        corsOptions.fAllowCredentials    = Memory::NullCoalesce (corsOptions.fAllowCredentials, kDefault_CORSOptions.fAllowCredentials);
        corsOptions.fAccessControlMaxAge = Memory::NullCoalesce (corsOptions.fAccessControlMaxAge, kDefault_CORSOptions.fAccessControlMaxAge);
        corsOptions.fAllowedHeaders      = Memory::NullCoalesce (corsOptions.fAllowedHeaders, kDefault_CORSOptions.fAllowedHeaders);
        corsOptions.fAllowedOrigins      = Memory::NullCoalesce (corsOptions.fAllowedOrigins, kDefault_CORSOptions.fAllowedOrigins);
        return corsOptions;
    }
}
Router::Router (const Sequence<Route>& routes, const CORSOptions& corsOptions)
    : inherited{make_shared<Rep_> (routes, FillIn_ (corsOptions))}
{
}

optional<RequestHandler> Router::Lookup (const Request& request) const
{
    return _GetRep<Rep_> ().Lookup_ (request, nullptr);
}
<commit_msg>cosmetic<commit_after>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2021.  All rights reserved
 */
#include "../StroikaPreComp.h"

#include "../../Foundation/Characters/Format.h"
#include "../../Foundation/Characters/ToString.h"
#include "../../Foundation/Execution/Exceptions.h"
#include "../../Foundation/IO/Network/HTTP/ClientErrorException.h"
#include "../../Foundation/IO/Network/HTTP/Headers.h"
#include "../../Foundation/IO/Network/HTTP/Methods.h"

#include "Router.h"

using namespace Stroika::Foundation;
using namespace Stroika::Foundation::Characters;
using namespace Stroika::Foundation::Containers;
using namespace Stroika::Foundation::Memory;
using namespace Stroika::Foundation::IO::Network;

using namespace Stroika::Frameworks;
using namespace Stroika::Frameworks::WebServer;

using HTTP::ClientErrorException;

// Comment this in to turn on aggressive noisy DbgTrace in this module
//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1

/*
 *  IMPLEMENTATION HINTS:
 *      A good place to look for a mature implementation of similar logic would be
 *          https://github.com/apache/cxf/blob/master/rt/rs/security/cors/src/main/java/org/apache/cxf/rs/security/cors/CrossOriginResourceSharingFilter.java
 */

namespace {
    String ExtractHostRelPath_ (const URI& url)
    {
        try {
            return url.GetAbsPath<String> ().SubString (1); // According to https://tools.ietf.org/html/rfc2616#section-5.1.2 - the URI must be abs_path
        }
        catch (...) {
            Execution::Throw (ClientErrorException{HTTP::StatusCodes::kBadRequest, L"request URI requires an absolute path"sv});
        }
    }
}

/*
 ********************************************************************************
 ******************************* Route::Matches *********************************
 ********************************************************************************
 */
bool Route::Matches (const Request& request, Sequence<String>* pathRegExpMatches) const
{
    return Matches (request.httpMethod (), ExtractHostRelPath_ (request.url ()), request, pathRegExpMatches);
}
bool Route::Matches (const String& method, const String& hostRelPath, const Request& request, Sequence<String>* pathRegExpMatches) const
{
    if (fVerbAndPathMatch_) {
        if (not method.Match (fVerbAndPathMatch_->first)) {
            return false;
        }
        return (pathRegExpMatches == nullptr)
                   ? hostRelPath.Match (fVerbAndPathMatch_->second)
                   : hostRelPath.Match (fVerbAndPathMatch_->second, pathRegExpMatches);
    }
    else if (fRequestMatch_) {
        return (*fRequestMatch_) (method, hostRelPath, request);
    }
    else {
        AssertNotReached ();
        return false;
    }
}

/*
 ********************************************************************************
 ************************* WebServer::Router::Rep_ ******************************
 ********************************************************************************
 */
struct Router::Rep_ : Interceptor::_IRep {
    static const optional<Set<String>> MapStartToNullOpt_ (const optional<Set<String>>& o)
    {
        // internally we treat missing as wildcard but caller may not, so map
        optional<Set<String>> m = o;
        if (m and m->Contains (CORSOptions::kAccessControlWildcard)) {
            m = nullopt;
        }
        if (m) {
            m = Set<String>{kHeaderNameEqualsComparer, *m};
        }
        return m;
    }
    // requires all optional values filled in on corsOptions
    Rep_ (const Sequence<Route>& routes, const CORSOptions& filledInCORSOptions)
        : fAllowedOrigins_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedOrigins)}
        , fAllowedHeaders_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedHeaders)}
        , fAccessControlAllowCredentialsValue_{*filledInCORSOptions.fAllowCredentials ? L"true"sv : L"false"sv}
        , fAccessControlMaxAgeValue_{Characters::Format (L"%d", *filledInCORSOptions.fAccessControlMaxAge)}
        , fRoutes_{routes}
    {
    }
    virtual void HandleMessage (Message* m) const override
    {
#if USE_NOISY_TRACE_IN_THIS_MODULE_
        Debug::TraceContextBumper ctx{L"Router::Rep_::HandleMessage", L"(...method=%s,url=%s)", m->GetRequestHTTPMethod ().c_str (), Characters::ToString (m->GetRequestURL ()).c_str ()};
#endif
        Sequence<String>         matches;
        optional<RequestHandler> handler = Lookup_ (m->request (), &matches);
        if (handler) {
            Handle_GET_ (m, matches, *handler);
        }
        else if (m->request ().httpMethod () == HTTP::Methods::kHead and Handle_HEAD_ (m)) {
            // handled
        }
        else if (m->request ().httpMethod () == HTTP::Methods::kOptions) {
            Handle_OPTIONS_ (m);
        }
        else {
            if (optional<Set<String>> o = GetAllowedMethodsForRequest_ (m->request ())) {
                // From 10.4.6 405 Method Not Allowed
                //      The method specified in the Request-Line is not allowed for the resource identified by the Request-URI.
                //      The response MUST include an Allow header containing a list of valid methods for the requested resource.
                Assert (not o->empty ());
                m->rwResponse ().UpdateHeader ([&] (auto* header) { RequireNotNull (header); header->allow = o; });
                Execution::Throw (ClientErrorException{HTTP::StatusCodes::kMethodNotAllowed});
            }
            else {
                DbgTrace (L"Router 404: (...url=%s)", Characters::ToString (m->request ().url ()).c_str ());
                Execution::Throw (ClientErrorException{HTTP::StatusCodes::kNotFound});
            }
        }
    }
    nonvirtual void HandleCORSInNormallyHandledMessage_ (const Request& request, Response& response) const
    {
        /*
         *  Origin and Access-Control-Allow-Origin
         *      documented here:    http://www.w3.org/TR/cors/#http-responses
         *
         *  IF CORS is being used, the request will contain an Origin header, and will expect a
         *  corresponding Access-Control-Allow-Origin response header. If the response header is missing
         *  that implies a CORS failure (but if we have no header in the request - presumably no matter)
         */
        optional<String> allowedOrigin;
        if (auto origin = request.headers ().origin ()) {
            if (fAllowedOrigins_.has_value ()) {
                // see https://fetch.spec.whatwg.org/#http-origin for hints about how to compare - not sure
                // may need to be more flexible about how we compare, but for now a good approximation... &&& @todo docs above link say how to compar
                String originStr = origin->ToString ();
                if (fAllowedOrigins_->Contains (originStr)) {
                    allowedOrigin = originStr;
                }
            }
            else {
                allowedOrigin = CORSOptions::kAccessControlWildcard;
            }
        }
        if (allowedOrigin) {
            response.UpdateHeader ([&] (auto* header) {
                RequireNotNull (header);
                header->accessControlAllowOrigin = allowedOrigin;
                if (fAllowedOrigins_) {
                    // see https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches to see why we need to add Vary response
                    // if response depends on origin (so not '*')
                    header->vary = Memory::NullCoalesce (header->vary ()) + String{HTTP::HeaderName::kOrigin};
                }
            });
        }
    }
    nonvirtual optional<RequestHandler> Lookup_ (const String& method, const String& hostRelPath, const Request& request, Sequence<String>* matches) const
    {
        for (Route r : fRoutes_) {
            if (r.Matches (method, hostRelPath, request, matches)) {
                return r.fHandler_;
            }
        }
        return nullopt;
    }
    nonvirtual optional<RequestHandler> Lookup_ (const Request& request, Sequence<String>* matches) const
    {
        return Lookup_ (request.httpMethod (), ExtractHostRelPath_ (request.url ()), request, matches);
    }
    nonvirtual optional<Set<String>> GetAllowedMethodsForRequest_ (const Request& request) const
    {
        String                   hostRelPath = ExtractHostRelPath_ (request.GetURL ());
        static const Set<String> kMethods2Try_{HTTP::Methods::kGet, HTTP::Methods::kPut, HTTP::Methods::kOptions, HTTP::Methods::kDelete, HTTP::Methods::kPost};
        Set<String>              methods;
        for (String method : kMethods2Try_) {
            for (Route r : fRoutes_) {
                if (r.Matches (method, hostRelPath, request)) {
                    methods.Add (method);
                }
            }
        }
        return methods.empty () ? nullopt : optional<Set<String>>{methods};
    }
    nonvirtual void Handle_GET_ (Message* message, const Sequence<String>& matches, const RequestHandler& handler) const
    {
        const Request& request  = message->request ();
        Response&      response = message->rwResponse ();
        (handler) (message, matches);
        HandleCORSInNormallyHandledMessage_ (request, response);
    }
    nonvirtual bool Handle_HEAD_ (Message* message) const
    {
        const Request&   request  = message->request ();
        Response&        response = message->rwResponse ();
        Sequence<String> matches;
        if (optional<RequestHandler> handler = Lookup_ (Methods::kGet, ExtractHostRelPath_ (request.url ()), request, &matches)) {
            // do someting to response so 'in HEAD mode' and won't write
            response.EnterHeadMode ();
            (*handler) (message, matches);
            HandleCORSInNormallyHandledMessage_ (request, response);
            return true;
        }
        return false;
    }
    nonvirtual void Handle_OPTIONS_ (Message* message) const
    {
        const Request& request  = message->request ();
        Response&      response = message->rwResponse ();
        // @todo note - This ignores - Access-Control-Request-Method - not sure how we are expected to use it?
        auto o = GetAllowedMethodsForRequest_ (request);
        if (o) {
            auto accessControlRequestHeaders = request.headers ().LookupOne (HeaderName::kAccessControlRequestHeaders);
            response.UpdateHeader ([this, &o, &accessControlRequestHeaders] (auto* header) {
                RequireNotNull (header);
                header->Set (HeaderName::kAccessControlAllowCredentials, fAccessControlAllowCredentialsValue_);
                if (accessControlRequestHeaders) {
                    if (fAllowedHeaders_) {
                        // intersect requested headers with those configured to permit
                        Iterable<String> requestAccessHeaders = accessControlRequestHeaders->Tokenize ({','});
                        auto             r                    = fAllowedHeaders_->Intersection (requestAccessHeaders);
                        if (r.empty ()) {
                            header->Set (HeaderName::kAccessControlAllowHeaders, String::Join (r));
                        }
                    }
                    else {
                        header->Set (HeaderName::kAccessControlAllowHeaders, *accessControlRequestHeaders);
                    }
                }
                header->Set (HeaderName::kAccessControlAllowMethods, String::Join (*o));
                header->Set (HeaderName::kAccessControlMaxAge, fAccessControlMaxAgeValue_);
            });
            HandleCORSInNormallyHandledMessage_ (request, response); // include access-origin-header
            response.SetStatus (IO::Network::HTTP::StatusCodes::kNoContent);
        }
        else {
            DbgTrace (L"Router 404: (...url=%s)", Characters::ToString (message->request ().url ()).c_str ());
            Execution::Throw (ClientErrorException{HTTP::StatusCodes::kNotFound});
        }
    }

    const optional<Set<String>> fAllowedOrigins_; // missing <==> '*'
    const optional<Set<String>> fAllowedHeaders_; // missing <==> '*'
    const String                fAccessControlAllowCredentialsValue_;
    const String                fAccessControlMaxAgeValue_;
    const Sequence<Route>       fRoutes_; // no need for synchronization cuz constant - just set on construction
};

/*
 ********************************************************************************
 *************************** WebServer::Router **********************************
 ********************************************************************************
 */
namespace {
    CORSOptions FillIn_ (CORSOptions corsOptions)
    {
        corsOptions.fAllowCredentials    = Memory::NullCoalesce (corsOptions.fAllowCredentials, kDefault_CORSOptions.fAllowCredentials);
        corsOptions.fAccessControlMaxAge = Memory::NullCoalesce (corsOptions.fAccessControlMaxAge, kDefault_CORSOptions.fAccessControlMaxAge);
        corsOptions.fAllowedHeaders      = Memory::NullCoalesce (corsOptions.fAllowedHeaders, kDefault_CORSOptions.fAllowedHeaders);
        corsOptions.fAllowedOrigins      = Memory::NullCoalesce (corsOptions.fAllowedOrigins, kDefault_CORSOptions.fAllowedOrigins);
        return corsOptions;
    }
}
Router::Router (const Sequence<Route>& routes, const CORSOptions& corsOptions)
    : inherited{make_shared<Rep_> (routes, FillIn_ (corsOptions))}
{
}

optional<RequestHandler> Router::Lookup (const Request& request) const
{
    return _GetRep<Rep_> ().Lookup_ (request, nullptr);
}
<|endoftext|>


<commit_before><?hh // strict

/**
 * This file is part of hhpack\process package.
 *
 * (c) Noritaka Horio <holy.shared.design@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace hhpack\process;

final class GeneralContext extends ProcessContext implements Context
{

    private DescriptorRegistry $descriptors;

    public function __construct(
        string $command,
        string $cwd = (string) getcwd(),
        ?environment $env = null
    )
    {
        $this->command = $command;
        $this->workingDirectory = $cwd;
        $this->enviromentVariables = new ImmMap($env);
        $this->pipeManager = new NullPipeManager();
        $this->descriptors = DescriptorRegistry::fromArray([
            new Descriptor(PipeType::Stdin, [ 'pipe', 'r' ]),
            new Descriptor(PipeType::Stdout, [ 'pipe', 'w' ]),
            new Descriptor(PipeType::Stderr, [ 'pipe', 'w' ])
        ]);
        $this->status = ProcessStatus::initialStatus();
    }

    public async function run() : Awaitable<ProcessResult>
    {
        $this->start();
        return await $this->wait();
    }

    public function start() : void
    {
        $pipeHandles = [];

        $this->process = proc_open(
            $this->command,
            $this->descriptors->toArray(),
            $pipeHandles
        );

        $this->pipeManager = $this->descriptors->createPipeManager($pipeHandles);
        $this->captureStatus();
    }

}
<commit_msg>throw exception<commit_after><?hh // strict

/**
 * This file is part of hhpack\process package.
 *
 * (c) Noritaka Horio <holy.shared.design@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace hhpack\process;

use RuntimeException;

final class GeneralContext extends ProcessContext implements Context
{

    private DescriptorRegistry $descriptors;

    public function __construct(
        string $command,
        string $cwd = (string) getcwd(),
        ?environment $env = null
    )
    {
        $this->command = $command;
        $this->workingDirectory = $cwd;
        $this->enviromentVariables = new ImmMap($env);
        $this->pipeManager = new NullPipeManager();
        $this->descriptors = DescriptorRegistry::fromArray([
            new Descriptor(PipeType::Stdin, [ 'pipe', 'r' ]),
            new Descriptor(PipeType::Stdout, [ 'pipe', 'w' ]),
            new Descriptor(PipeType::Stderr, [ 'pipe', 'w' ])
        ]);
        $this->status = ProcessStatus::initialStatus();
    }

    public async function run() : Awaitable<ProcessResult>
    {
        $this->start();
        return await $this->wait();
    }

    public function start() : void
    {
        $pipeHandles = [];

        $this->process = proc_open(
            $this->command,
            $this->descriptors->toArray(),
            $pipeHandles
        );

        if (is_resource($this->process) === false) {
            throw new RuntimeException('Failed to start of process');
        }

        $this->pipeManager = $this->descriptors->createPipeManager($pipeHandles);
        $this->captureStatus();
    }

}
<|endoftext|>