cmsStage* CMSEXPORT cmsStageAllocCLutFloatGranular(cmsContext ContextID, const cmsUInt32Number clutPoints[], cmsUInt32Number inputChan, cmsUInt32Number outputChan, const cmsFloat32Number* Table)
{
    cmsUInt32Number i, n;
    _cmsStageCLutData* NewElem;
    cmsStage* NewMPE;

    _cmsAssert(clutPoints != NULL);

    if (inputChan > MAX_INPUT_DIMENSIONS) {
        cmsSignalError(ContextID, cmsERROR_RANGE, "Too many input channels (%d channels, max=%d)", inputChan, MAX_INPUT_DIMENSIONS);
        return NULL;
    }

    NewMPE = _cmsStageAllocPlaceholder(ContextID, cmsSigCLutElemType, inputChan, outputChan,
                                             EvaluateCLUTfloat, CLUTElemDup, CLutElemTypeFree, NULL);
    if (NewMPE == NULL) return NULL;

    NewElem = (_cmsStageCLutData*) _cmsMallocZero(ContextID, sizeof(_cmsStageCLutData));
    if (NewElem == NULL) {
        cmsStageFree(NewMPE);
        return NULL;
    }

    NewMPE ->Data  = (void*) NewElem;

    // There is a potential integer overflow on conputing n and nEntries.
    NewElem -> nEntries = n = outputChan * CubeSize(clutPoints, inputChan);
    NewElem -> HasFloatValues = TRUE;

    if (n == 0) {
        cmsStageFree(NewMPE);
        return NULL;
    }

    NewElem ->Tab.TFloat  = (cmsFloat32Number*) _cmsCalloc(ContextID, n, sizeof(cmsFloat32Number));
    if (NewElem ->Tab.TFloat == NULL) {
        cmsStageFree(NewMPE);
        return NULL;
    }

    if (Table != NULL) {
        for (i=0; i < n; i++) {
            NewElem ->Tab.TFloat[i] = Table[i];
        }
    }

    NewElem ->Params = _cmsComputeInterpParamsEx(ContextID, clutPoints,  inputChan, outputChan, NewElem ->Tab.TFloat, CMS_LERP_FLAGS_FLOAT);
    if (NewElem ->Params == NULL) {
        cmsStageFree(NewMPE);
        return NULL;
    }

    return NewMPE;
}


void FromFloatTo16(const cmsFloat32Number In[], cmsUInt16Number Out[], cmsUInt32Number n)
{
    cmsUInt32Number i;

    for (i=0; i < n; i++) {
        Out[i] = _cmsQuickSaturateWord(In[i] * 65535.0);
    }
}


cmsStage* _cmsStageAllocLabV2ToV4(cmsContext ContextID)
{
    static const cmsFloat64Number V2ToV4[] = { 65535.0/65280.0, 0, 0,
                                     0, 65535.0/65280.0, 0,
                                     0, 0, 65535.0/65280.0
                                     };

    cmsStage *mpe = cmsStageAllocMatrix(ContextID, 3, 3, V2ToV4, NULL);

    if (mpe == NULL) return mpe;
    mpe ->Implements = cmsSigLabV2toV4;
    return mpe;
}


uint16_t read16(uint8_t* arr, int pos,  int swapBytes)
{
    uint8_t b1 = arr[pos];
    uint8_t b2 = arr[pos+1];

    return (swapBytes) ?  ((b2 << 8) | b1) : ((b1 << 8) | b2);
}


cmsBool HandlePhotoshopAPP13(jpeg_saved_marker_ptr ptr)
{
    while (ptr) {

        if (ptr -> marker == (JPEG_APP0 + 13) && ptr -> data_length > 9)
        {
            JOCTET FAR* data = ptr -> data;

            if(GETJOCTET(data[0]) == 0x50 &&
               GETJOCTET(data[1]) == 0x68 &&
               GETJOCTET(data[2]) == 0x6F &&
               GETJOCTET(data[3]) == 0x74 &&
               GETJOCTET(data[4]) == 0x6F &&
               GETJOCTET(data[5]) == 0x73 &&
               GETJOCTET(data[6]) == 0x68 &&
               GETJOCTET(data[7]) == 0x6F &&
               GETJOCTET(data[8]) == 0x70) {

                ProcessPhotoshopAPP13(data, ptr -> data_length);
                return TRUE;
            }
        }

        ptr = ptr -> next;
    }

    return FALSE;
}


cmsPipeline* _cmsReadDevicelinkLUT(cmsHPROFILE hProfile, int Intent)
{
    cmsPipeline* Lut;
    cmsTagTypeSignature OriginalType;
    cmsTagSignature tag16    = Device2PCS16[Intent];
    cmsTagSignature tagFloat = Device2PCSFloat[Intent];
    cmsContext ContextID = cmsGetProfileContextID(hProfile);

    // On named color, take the appropiate tag
    if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {

        cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*) cmsReadTag(hProfile, cmsSigNamedColor2Tag);

        if (nc == NULL) return NULL;

        Lut = cmsPipelineAlloc(ContextID, 0, 0);
        if (Lut == NULL)
            goto Error;

	if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, FALSE)))
            goto Error;
        if (cmsGetColorSpace(hProfile) == cmsSigLabData)
            if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)))
                goto Error;
        return Lut;
Error:
        cmsPipelineFree(Lut);
        cmsFreeNamedColorList(nc);
        return NULL;
    }

    if (cmsIsTag(hProfile, tagFloat)) {  // Float tag takes precedence

        // Floating point LUT are always V
        return _cmsReadFloatDevicelinkTag(hProfile, tagFloat);
    }

    tagFloat = Device2PCSFloat[0];
    if (cmsIsTag(hProfile, tagFloat)) {

        return cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat));
    }

    if (!cmsIsTag(hProfile, tag16)) {  // Is there any LUT-Based table?

        tag16    = Device2PCS16[0];
        if (!cmsIsTag(hProfile, tag16)) return NULL;
    }

    // Check profile version and LUT type. Do the necessary adjustments if needed

    // Read the tag
    Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16);
    if (Lut == NULL) return NULL;

    // The profile owns the Lut, so we need to copy it
    Lut = cmsPipelineDup(Lut);
    if (Lut == NULL) return NULL;

     // Now it is time for a controversial stuff. I found that for 3D LUTS using
     // Lab used as indexer space,  trilinear interpolation should be used
    if (cmsGetColorSpace(hProfile) == cmsSigLabData)
                        ChangeInterpolationToTrilinear(Lut);

    // After reading it, we have info about the original type
    OriginalType =  _cmsGetTagTrueType(hProfile, tag16);

    // We need to adjust data for Lab16 on output
    if (OriginalType != cmsSigLut16Type) return Lut;

    // Here it is possible to get Lab on both sides

    if (cmsGetPCS(hProfile) == cmsSigLabData) {
        if(!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)))
            goto Error2;
    }

    if (cmsGetColorSpace(hProfile) == cmsSigLabData) {
        if(!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)))
            goto Error2;
    }

    return Lut;
Error2:
    cmsPipelineFree(Lut);
    return NULL;
}


CMSAPI cmsHPROFILE   CMSEXPORT cmsCreateBCHSWabstractProfile(int nLUTPoints,
                                                             cmsFloat64Number Bright,
                                                             cmsFloat64Number Contrast,
                                                             cmsFloat64Number Hue,
                                                             cmsFloat64Number Saturation,
                                                             int TempSrc,
                                                             int TempDest)
{
    return cmsCreateBCHSWabstractProfileTHR(NULL, nLUTPoints, Bright, Contrast, Hue, Saturation, TempSrc, TempDest);
}


cmsBool OptimizeByResampling(cmsPipeline** Lut, cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags)
{
    cmsPipeline* Src = NULL;
    cmsPipeline* Dest = NULL;
    cmsStage* mpe;
    cmsStage* CLUT;
    cmsStage *KeepPreLin = NULL, *KeepPostLin = NULL;
    int nGridPoints;
    cmsColorSpaceSignature ColorSpace, OutputColorSpace;
    cmsStage *NewPreLin = NULL;
    cmsStage *NewPostLin = NULL;
    _cmsStageCLutData* DataCLUT;
    cmsToneCurve** DataSetIn;
    cmsToneCurve** DataSetOut;
    Prelin16Data* p16;

    // This is a loosy optimization! does not apply in floating-point cases
    if (_cmsFormatterIsFloat(*InputFormat) || _cmsFormatterIsFloat(*OutputFormat)) return FALSE;

    ColorSpace       = _cmsICCcolorSpace(T_COLORSPACE(*InputFormat));
    OutputColorSpace = _cmsICCcolorSpace(T_COLORSPACE(*OutputFormat));
    nGridPoints      = _cmsReasonableGridpointsByColorspace(ColorSpace, *dwFlags);

    // For empty LUTs, 2 points are enough
    if (cmsPipelineStageCount(*Lut) == 0)
        nGridPoints = 2;

    Src = *Lut;

   // Named color pipelines cannot be optimized either
   for (mpe = cmsPipelineGetPtrToFirstStage(Src);
         mpe != NULL;
         mpe = cmsStageNext(mpe)) {
            if (cmsStageType(mpe) == cmsSigNamedColorElemType) return FALSE;
    }

    // Allocate an empty LUT
    Dest =  cmsPipelineAlloc(Src ->ContextID, Src ->InputChannels, Src ->OutputChannels);
    if (!Dest) return FALSE;

    // Prelinearization tables are kept unless indicated by flags
    if (*dwFlags & cmsFLAGS_CLUT_PRE_LINEARIZATION) {

        // Get a pointer to the prelinearization element
        cmsStage* PreLin = cmsPipelineGetPtrToFirstStage(Src);

        // Check if suitable
        if (PreLin ->Type == cmsSigCurveSetElemType) {

            // Maybe this is a linear tram, so we can avoid the whole stuff
            if (!AllCurvesAreLinear(PreLin)) {

                // All seems ok, proceed.
                NewPreLin = cmsStageDup(PreLin);
                if(!cmsPipelineInsertStage(Dest, cmsAT_BEGIN, NewPreLin))
                    goto Error;

                // Remove prelinearization. Since we have duplicated the curve
                // in destination LUT, the sampling shoud be applied after this stage.
                cmsPipelineUnlinkStage(Src, cmsAT_BEGIN, &KeepPreLin);
            }
        }
    }

    // Allocate the CLUT
    CLUT = cmsStageAllocCLut16bit(Src ->ContextID, nGridPoints, Src ->InputChannels, Src->OutputChannels, NULL);
    if (CLUT == NULL) return FALSE;

    // Add the CLUT to the destination LUT
    if (!cmsPipelineInsertStage(Dest, cmsAT_END, CLUT)) {
        goto Error;
    }

    // Postlinearization tables are kept unless indicated by flags
    if (*dwFlags & cmsFLAGS_CLUT_POST_LINEARIZATION) {

        // Get a pointer to the postlinearization if present
        cmsStage* PostLin = cmsPipelineGetPtrToLastStage(Src);

        // Check if suitable
        if (cmsStageType(PostLin) == cmsSigCurveSetElemType) {

            // Maybe this is a linear tram, so we can avoid the whole stuff
            if (!AllCurvesAreLinear(PostLin)) {

                // All seems ok, proceed.
                NewPostLin = cmsStageDup(PostLin);
                if (!cmsPipelineInsertStage(Dest, cmsAT_END, NewPostLin))
                    goto Error;

                // In destination LUT, the sampling shoud be applied after this stage.
                cmsPipelineUnlinkStage(Src, cmsAT_END, &KeepPostLin);
            }
        }
    }

    // Now its time to do the sampling. We have to ignore pre/post linearization
    // The source LUT whithout pre/post curves is passed as parameter.
    if (!cmsStageSampleCLut16bit(CLUT, XFormSampler16, (void*) Src, 0)) {
Error:
        // Ops, something went wrong, Restore stages
        if (KeepPreLin != NULL) {
            if (!cmsPipelineInsertStage(Src, cmsAT_BEGIN, KeepPreLin)) {
                assert("This never happens" == NULL);
            }
	}
        if (KeepPostLin != NULL) {
            if (!cmsPipelineInsertStage(Src, cmsAT_END,   KeepPostLin)) {
                assert("This never happens" == NULL);
            }
	}
        cmsPipelineFree(Dest);
        return FALSE;
    }

    // Done.

    if (KeepPreLin != NULL) cmsStageFree(KeepPreLin);
    if (KeepPostLin != NULL) cmsStageFree(KeepPostLin);
    cmsPipelineFree(Src);

    DataCLUT = (_cmsStageCLutData*) CLUT ->Data;

    if (NewPreLin == NULL) DataSetIn = NULL;
    else DataSetIn = ((_cmsStageToneCurvesData*) NewPreLin ->Data) ->TheCurves;

    if (NewPostLin == NULL) DataSetOut = NULL;
    else  DataSetOut = ((_cmsStageToneCurvesData*) NewPostLin ->Data) ->TheCurves;

    if (DataSetIn == NULL && DataSetOut == NULL) {

        _cmsPipelineSetOptimizationParameters(Dest, (_cmsOPTeval16Fn) DataCLUT->Params->Interpolation.Lerp16, DataCLUT->Params, NULL, NULL);
    }
    else {

        p16 = PrelinOpt16alloc(Dest ->ContextID,
                               DataCLUT ->Params,
                               Dest ->InputChannels,
                               DataSetIn,
                               Dest ->OutputChannels,
                               DataSetOut);

        _cmsPipelineSetOptimizationParameters(Dest, PrelinEval16, (void*) p16, PrelinOpt16free, Prelin16dup);
    }

    // Don't fix white on absolute colorimetric
    if (Intent == INTENT_ABSOLUTE_COLORIMETRIC)
        *dwFlags |= cmsFLAGS_NOWHITEONWHITEFIXUP;

    if (!(*dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP)) {

        FixWhiteMisalignment(Dest, ColorSpace, OutputColorSpace);
    }

    *Lut = Dest;
    return TRUE;

    cmsUNUSED_PARAMETER(Intent);
}


void *Type_LUT16_Read(struct _cms_typehandler_struct* self, cmsIOHANDLER* io, cmsUInt32Number* nItems, cmsUInt32Number SizeOfTag)
{
    cmsUInt8Number InputChannels, OutputChannels, CLUTpoints;
    cmsPipeline* NewLUT = NULL;
    cmsUInt32Number nTabSize;
    cmsFloat64Number Matrix[3*3];
    cmsUInt16Number InputEntries, OutputEntries;

    *nItems = 0;

    if (!_cmsReadUInt8Number(io, &InputChannels)) return NULL;
    if (!_cmsReadUInt8Number(io, &OutputChannels)) return NULL;
    if (!_cmsReadUInt8Number(io, &CLUTpoints)) return NULL;   // 255 maximum

    // Padding
    if (!_cmsReadUInt8Number(io, NULL)) return NULL;

    // Do some checking
    if (InputChannels > cmsMAXCHANNELS)  goto Error;
    if (OutputChannels > cmsMAXCHANNELS) goto Error;

    // Allocates an empty LUT
    NewLUT = cmsPipelineAlloc(self ->ContextID, InputChannels, OutputChannels);
    if (NewLUT == NULL) goto Error;

    // Read the Matrix
    if (!_cmsRead15Fixed16Number(io,  &Matrix[0])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[1])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[2])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[3])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[4])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[5])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[6])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[7])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[8])) goto Error;

    // Only operates on 3 channels
    if ((InputChannels == 3) && !_cmsMAT3isIdentity((cmsMAT3*) Matrix)) {

        if (!cmsPipelineInsertStage(NewLUT, cmsAT_END, cmsStageAllocMatrix(self ->ContextID, 3, 3, Matrix, NULL)))
            goto Error;
    }

    if (!_cmsReadUInt16Number(io, &InputEntries)) goto Error;
    if (!_cmsReadUInt16Number(io, &OutputEntries)) goto Error;

    if (InputEntries > 0x7FFF || OutputEntries > 0x7FFF) goto Error;
    if (CLUTpoints == 1) goto Error; // Impossible value, 0 for no CLUT and then 2 at least

    // Get input tables
    if (!Read16bitTables(self ->ContextID, io,  NewLUT, InputChannels, InputEntries)) goto Error;

    // Get 3D CLUT
    nTabSize = uipow(OutputChannels, CLUTpoints, InputChannels);
    if (nTabSize == (cmsUInt32Number) -1) goto Error;
    if (nTabSize > 0) {

        cmsUInt16Number *T;

        T  = (cmsUInt16Number*) _cmsCalloc(self ->ContextID, nTabSize, sizeof(cmsUInt16Number));
        if (T  == NULL) goto Error;

        if (!_cmsReadUInt16Array(io, nTabSize, T)) {
            _cmsFree(self ->ContextID, T);
            goto Error;
        }

	if (!cmsPipelineInsertStage(NewLUT, cmsAT_END, cmsStageAllocCLut16bit(self ->ContextID, CLUTpoints, InputChannels, OutputChannels, T))) {
             _cmsFree(self ->ContextID, T);
            goto Error;
	}
        _cmsFree(self ->ContextID, T);
    }

    // Get output tables
    if (!Read16bitTables(self ->ContextID, io,  NewLUT, OutputChannels, OutputEntries)) goto Error;

    *nItems = 1;
    return NewLUT;

Error:
    if (NewLUT != NULL) cmsPipelineFree(NewLUT);
    return NULL;

    cmsUNUSED_PARAMETER(SizeOfTag);
}


int EstimateTAC(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void * Cargo)
{
    cmsTACestimator* bp = (cmsTACestimator*) Cargo;
    cmsFloat32Number RoundTrip[cmsMAXCHANNELS];
    cmsUInt32Number i;
    cmsFloat32Number Sum;

    // Evaluate the xform
    cmsDoTransform(bp->hRoundTrip, In, RoundTrip, 1);

    // All all amounts of ink
    for (Sum=0, i=0; i < bp ->nOutputChans; i++)
            Sum += RoundTrip[i];

    // If above maximum, keep track of input values
    if (Sum > bp ->MaxTAC) {

            bp ->MaxTAC = Sum;

            for (i=0; i < bp ->nOutputChans; i++) {
                bp ->MaxInput[i] = In[i];
            }
    }

    return TRUE;

    cmsUNUSED_PARAMETER(Out);
}


cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile)
{
    cmsPipeline* Lut;
    cmsToneCurve *Shapes[3], *InvShapes[3];
    cmsMAT3 Mat, Inv;
    int i, j;
    cmsContext ContextID = cmsGetProfileContextID(hProfile);

    if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile))
        return NULL;

    if (!_cmsMAT3inverse(&Mat, &Inv))
        return NULL;

    // XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so
    // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of
    // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0;

    for (i=0; i < 3; i++)
        for (j=0; j < 3; j++)
            Inv.v[i].n[j] *= OutpAdj;

    Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag);
    Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag);
    Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag);

    if (!Shapes[0] || !Shapes[1] || !Shapes[2])
        return NULL;

    InvShapes[0] = cmsReverseToneCurve(Shapes[0]);
    InvShapes[1] = cmsReverseToneCurve(Shapes[1]);
    InvShapes[2] = cmsReverseToneCurve(Shapes[2]);

    if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) {
        return NULL;
    }

    Lut = cmsPipelineAlloc(ContextID, 3, 3);
    if (Lut != NULL) {

        // Note that it is certainly possible a single profile would have a LUT based
        // tag for output working in lab and a matrix-shaper for the fallback cases.
        // This is not allowed by the spec, but this code is tolerant to those cases
        if (cmsGetPCS(hProfile) == cmsSigLabData) {

             if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID)))
                 goto Error;
        }

        if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)) ||
            !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, InvShapes)))
	    goto Error;
    }

    cmsFreeToneCurveTriple(InvShapes);
    return Lut;
Error:
    cmsFreeToneCurveTriple(InvShapes);
    cmsPipelineFree(Lut);
    return NULL;
}


cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent)
{
    cmsTagTypeSignature OriginalType;
    cmsTagSignature tag16    = Device2PCS16[Intent];
    cmsTagSignature tagFloat = Device2PCSFloat[Intent];
    cmsContext ContextID = cmsGetProfileContextID(hProfile);

    // On named color, take the appropiate tag
    if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {

        cmsPipeline* Lut;
        cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*) cmsReadTag(hProfile, cmsSigNamedColor2Tag);

        if (nc == NULL) return NULL;

        Lut = cmsPipelineAlloc(ContextID, 0, 0);
        if (Lut == NULL) {
            cmsFreeNamedColorList(nc);
            return NULL;
        }

        if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, TRUE)) ||
            !cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) {
            cmsPipelineFree(Lut);
            return NULL;
        }
        return Lut;
    }

    if (cmsIsTag(hProfile, tagFloat)) {  // Float tag takes precedence

        // Floating point LUT are always V4, but the encoding range is no
        // longer 0..1.0, so we need to add an stage depending on the color space
         return _cmsReadFloatInputTag(hProfile, tagFloat);
    }

    // Revert to perceptual if no tag is found
    if (!cmsIsTag(hProfile, tag16)) {
        tag16 = Device2PCS16[0];
    }

    if (cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table?

        // Check profile version and LUT type. Do the necessary adjustments if needed

        // First read the tag
        cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16);
        if (Lut == NULL) return NULL;

        // After reading it, we have now info about the original type
        OriginalType =  _cmsGetTagTrueType(hProfile, tag16);

        // The profile owns the Lut, so we need to copy it
        Lut = cmsPipelineDup(Lut);

        // We need to adjust data only for Lab16 on output
        if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData)
            return Lut;

        // If the input is Lab, add also a conversion at the begin
        if (cmsGetColorSpace(hProfile) == cmsSigLabData &&
            !cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)))
            goto Error;

        // Add a matrix for conversion V2 to V4 Lab PCS
        if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)))
            goto Error;

        return Lut;
Error:
        cmsPipelineFree(Lut);
        return NULL;
    }

    // Lut was not found, try to create a matrix-shaper

    // Check if this is a grayscale profile.
    if (cmsGetColorSpace(hProfile) == cmsSigGrayData) {

        // if so, build appropiate conversion tables.
        // The tables are the PCS iluminant, scaled across GrayTRC
        return BuildGrayInputMatrixPipeline(hProfile);
    }

    // Not gray, create a normal matrix-shaper
    return BuildRGBInputMatrixShaper(hProfile);
}


const cmsMLU* GetInfo(cmsHPROFILE hProfile, cmsInfoType Info)
{
    cmsTagSignature sig;

    switch (Info) {

    case cmsInfoDescription:
        sig = cmsSigProfileDescriptionTag;
        break;

    case cmsInfoManufacturer:
        sig = cmsSigDeviceMfgDescTag;
        break;

    case cmsInfoModel:
        sig = cmsSigDeviceModelDescTag;
         break;

    case cmsInfoCopyright:
        sig = cmsSigCopyrightTag;
        break;

    default: return NULL;
    }

    return (cmsMLU*) cmsReadTag(hProfile, sig);
}


cmsPipeline* _cmsReadFloatOutputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat)
{
    cmsContext ContextID       = cmsGetProfileContextID(hProfile);
    cmsPipeline* Lut           = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat));
    cmsColorSpaceSignature PCS = cmsGetPCS(hProfile);
    cmsColorSpaceSignature dataSpace = cmsGetColorSpace(hProfile);

    if (Lut == NULL) return NULL;

    // If PCS is Lab or XYZ, the floating point tag is accepting data in the space encoding,
    // and since the formatter has already accomodated to 0..1.0, we should undo this change
    if ( PCS == cmsSigLabData)
    {
        if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID)))
            goto Error;
    }
    else
        if (PCS == cmsSigXYZData)
        {
            if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID)))
                goto Error;
        }

    // the output can be Lab or XYZ, in which case normalisation is needed on the end of the pipeline
    if ( dataSpace == cmsSigLabData)
    {
        if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)))
            goto Error;
    }
    else if ( dataSpace == cmsSigXYZData)
    {
        if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)))
            goto Error;
    }

    return Lut;
Error:
    cmsPipelineFree(Lut);
    return NULL;
}


cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
{
    cmsHPROFILE hProfile;
    cmsPipeline* LUT = NULL;

    hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
    if (hProfile == NULL) return NULL;

    cmsSetProfileVersion(hProfile, 4.3);

    cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
    cmsSetColorSpace(hProfile,  cmsSigLabData);
    cmsSetPCS(hProfile,         cmsSigLabData);

    if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;

    // An empty LUTs is all we need
    LUT = cmsPipelineAlloc(ContextID, 3, 3);
    if (LUT == NULL) goto Error;

    if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)))
        goto Error;

    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
    cmsPipelineFree(LUT);

    return hProfile;

Error:

    if (LUT != NULL)
        cmsPipelineFree(LUT);

    if (hProfile != NULL)
        cmsCloseProfile(hProfile);

    return NULL;
}


cmsBool CheckOne(const cmsAllowedLUT* Tab, const cmsPipeline* Lut)
{
    cmsStage* mpe;
    int n;

    for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) {

        if (n > Tab ->nTypes) return FALSE;
        if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE;
    }

    return (n == Tab ->nTypes);
}


cmsHPROFILE CMSEXPORT cmsCreateLab2Profile(const cmsCIExyY* WhitePoint)
{
    return cmsCreateLab2ProfileTHR(NULL, WhitePoint);
}


cmsBool  Type_LUT16_Write(struct _cms_typehandler_struct* self, cmsIOHANDLER* io, void* Ptr, cmsUInt32Number nItems)
{
    cmsUInt32Number nTabSize;
    cmsPipeline* NewLUT = (cmsPipeline*) Ptr;
    cmsStage* mpe;
    _cmsStageToneCurvesData* PreMPE = NULL, *PostMPE = NULL;
    _cmsStageMatrixData* MatMPE = NULL;
    _cmsStageCLutData* clut = NULL;
    int InputChannels, OutputChannels, clutPoints;

    // Disassemble the LUT into components.
    mpe = NewLUT -> Elements;
    if (mpe != NULL && mpe ->Type == cmsSigMatrixElemType) {

        MatMPE = (_cmsStageMatrixData*) mpe ->Data;
        mpe = mpe -> Next;
    }

    if (mpe != NULL && mpe ->Type == cmsSigCurveSetElemType) {
        PreMPE = (_cmsStageToneCurvesData*) mpe ->Data;
        mpe = mpe -> Next;
    }

    if (mpe != NULL && mpe ->Type == cmsSigCLutElemType) {
        clut  = (_cmsStageCLutData*) mpe -> Data;
        mpe = mpe ->Next;
    }

    if (mpe != NULL && mpe ->Type == cmsSigCurveSetElemType) {
        PostMPE = (_cmsStageToneCurvesData*) mpe ->Data;
        mpe = mpe -> Next;
    }

    // That should be all
    if (mpe != NULL) {
        cmsSignalError(mpe->ContextID, cmsERROR_UNKNOWN_EXTENSION, "LUT is not suitable to be saved as LUT16");
        return FALSE;
    }

    InputChannels  = cmsPipelineInputChannels(NewLUT);
    OutputChannels = cmsPipelineOutputChannels(NewLUT);

    if (clut == NULL)
        clutPoints = 0;
    else
        clutPoints    = clut->Params->nSamples[0];

    if (!_cmsWriteUInt8Number(io, (cmsUInt8Number) InputChannels)) return FALSE;
    if (!_cmsWriteUInt8Number(io, (cmsUInt8Number) OutputChannels)) return FALSE;
    if (!_cmsWriteUInt8Number(io, (cmsUInt8Number) clutPoints)) return FALSE;
    if (!_cmsWriteUInt8Number(io, 0)) return FALSE; // Padding

    if (MatMPE != NULL) {

        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[0])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[1])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[2])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[3])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[4])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[5])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[6])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[7])) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, MatMPE -> Double[8])) return FALSE;
    }
    else {

        if (!_cmsWrite15Fixed16Number(io, 1)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 0)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 0)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 0)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 1)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 0)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 0)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 0)) return FALSE;
        if (!_cmsWrite15Fixed16Number(io, 1)) return FALSE;
    }

    if (PreMPE != NULL) {
        if (!_cmsWriteUInt16Number(io, (cmsUInt16Number) PreMPE ->TheCurves[0]->nEntries)) return FALSE;
    } else {
            if (!_cmsWriteUInt16Number(io, 0)) return FALSE;
    }

    if (PostMPE != NULL) {
        if (!_cmsWriteUInt16Number(io, (cmsUInt16Number) PostMPE ->TheCurves[0]->nEntries)) return FALSE;
    } else {
        if (!_cmsWriteUInt16Number(io, 0)) return FALSE;

    }

    // The prelinearization table

    if (PreMPE != NULL) {
        if (!Write16bitTables(self ->ContextID, io, PreMPE)) return FALSE;
    }

    nTabSize = uipow(OutputChannels, clutPoints, InputChannels);
    if (nTabSize == (cmsUInt32Number) -1) return FALSE;
    if (nTabSize > 0) {
        // The 3D CLUT.
        if (clut != NULL) {
            if (!_cmsWriteUInt16Array(io, nTabSize, clut->Tab.T)) return FALSE;
        }
    }

    // The postlinearization table
    if (PostMPE != NULL) {
        if (!Write16bitTables(self ->ContextID, io, PostMPE)) return FALSE;
    }

    return TRUE;

    cmsUNUSED_PARAMETER(nItems);
}


void CMSEXPORT cmsFreeToneCurveTriple(cmsToneCurve* Curve[3])
{

    _cmsAssert(Curve != NULL);

    if (Curve[0] != NULL) cmsFreeToneCurve(Curve[0]);
    if (Curve[1] != NULL) cmsFreeToneCurve(Curve[1]);
    if (Curve[2] != NULL) cmsFreeToneCurve(Curve[2]);

    Curve[0] = Curve[1] = Curve[2] = NULL;
}


cmsBool CMSEXPORT cmsIsToneCurveLinear(const cmsToneCurve* Curve)
{
    cmsUInt32Number i;
    int diff;

    _cmsAssert(Curve != NULL);

    for (i=0; i < Curve ->nEntries; i++) {

        diff = abs((int) Curve->Table16[i] - (int) _cmsQuantizeVal(i, Curve ->nEntries));
        if (diff > 0x0f)
            return FALSE;
    }

    return TRUE;
}


_cmsParametricCurvesCollection *GetParametricCurveByType(int Type, int* index)
{
    _cmsParametricCurvesCollection* c;
    int Position;

    for (c = ParametricCurves; c != NULL; c = c ->Next) {

        Position = IsInSet(Type, c);

        if (Position != -1) {
            if (index != NULL)
                *index = Position;
            return c;
        }
    }

    return NULL;
}


const cmsUInt16Number* CMSEXPORT cmsGetToneCurveEstimatedTable(const cmsToneCurve* t)
{
    _cmsAssert(t != NULL);
    return t ->Table16;
}


cmsBool  CMSEXPORT cmsIsToneCurveMonotonic(const cmsToneCurve* t)
{
    int n;
    int i, last;
    cmsBool lDescending;

    _cmsAssert(t != NULL);

    // Degenerated curves are monotonic? Ok, let's pass them
    n = t ->nEntries;
    if (n < 2) return TRUE;

    // Curve direction
    lDescending = cmsIsToneCurveDescending(t);

    if (lDescending) {

        last = t ->Table16[0];

        for (i = 1; i < n; i++) {

            if (t ->Table16[i] - last > 2) // We allow some ripple
                return FALSE;
            else
                last = t ->Table16[i];

        }
    }
    else {

        last = t ->Table16[n-1];

        for (i = n-2; i >= 0; --i) {

            if (t ->Table16[i] - last > 2)
                return FALSE;
            else
                last = t ->Table16[i];

        }
    }

    return TRUE;
}


cmsBool _cmsReadHeader(_cmsICCPROFILE* Icc)
{
    cmsTagEntry Tag;
    cmsICCHeader Header;
    cmsUInt32Number i, j;
    cmsUInt32Number HeaderSize;
    cmsIOHANDLER* io = Icc ->IOhandler;
    cmsUInt32Number TagCount;

    // Read the header
    if (io -> Read(io, &Header, sizeof(cmsICCHeader), 1) != 1) {
        return FALSE;
    }

    // Validate file as an ICC profile
    if (_cmsAdjustEndianess32(Header.magic) != cmsMagicNumber) {
        cmsSignalError(Icc ->ContextID, cmsERROR_BAD_SIGNATURE, "not an ICC profile, invalid signature");
        return FALSE;
    }

    // Adjust endianess of the used parameters
    Icc -> DeviceClass     = (cmsProfileClassSignature) _cmsAdjustEndianess32(Header.deviceClass);
    Icc -> ColorSpace      = (cmsColorSpaceSignature)   _cmsAdjustEndianess32(Header.colorSpace);
    Icc -> PCS             = (cmsColorSpaceSignature)   _cmsAdjustEndianess32(Header.pcs);

    Icc -> RenderingIntent = _cmsAdjustEndianess32(Header.renderingIntent);
    Icc -> flags           = _cmsAdjustEndianess32(Header.flags);
    Icc -> manufacturer    = _cmsAdjustEndianess32(Header.manufacturer);
    Icc -> model           = _cmsAdjustEndianess32(Header.model);
    Icc -> creator         = _cmsAdjustEndianess32(Header.creator);

    _cmsAdjustEndianess64(&Icc -> attributes, &Header.attributes);
    Icc -> Version         = _cmsAdjustEndianess32(Header.version);

    // Get size as reported in header
    HeaderSize = _cmsAdjustEndianess32(Header.size);

    // Make sure HeaderSize is lower than profile size
    if (HeaderSize >= Icc ->IOhandler ->ReportedSize)
            HeaderSize = Icc ->IOhandler ->ReportedSize;

    // Get creation date/time
    _cmsDecodeDateTimeNumber(&Header.date, &Icc ->Created);

    // The profile ID are 32 raw bytes
    memmove(Icc ->ProfileID.ID32, Header.profileID.ID32, 16);

    // Read tag directory
    if (!_cmsReadUInt32Number(io, &TagCount)) return FALSE;
    if (TagCount > MAX_TABLE_TAG) {

        cmsSignalError(Icc ->ContextID, cmsERROR_RANGE, "Too many tags (%d)", TagCount);
        return FALSE;
    }

    // Read tag directory
    Icc -> TagCount = 0;
    for (i=0; i < TagCount; i++) {

        if (!_cmsReadUInt32Number(io, (cmsUInt32Number *) &Tag.sig)) return FALSE;
        if (!_cmsReadUInt32Number(io, &Tag.offset)) return FALSE;
        if (!_cmsReadUInt32Number(io, &Tag.size)) return FALSE;

        // Perform some sanity check. Offset + size should fall inside file.
        if (Tag.offset + Tag.size > HeaderSize ||
            Tag.offset + Tag.size < Tag.offset)
                  continue;

        Icc -> TagNames[Icc ->TagCount]   = Tag.sig;
        Icc -> TagOffsets[Icc ->TagCount] = Tag.offset;
        Icc -> TagSizes[Icc ->TagCount]   = Tag.size;

       // Search for links
        for (j=0; j < Icc ->TagCount; j++) {

            if ((Icc ->TagOffsets[j] == Tag.offset) &&
                (Icc ->TagSizes[j]   == Tag.size)) {

                Icc ->TagLinked[Icc ->TagCount] = Icc ->TagNames[j];
            }

        }

        Icc ->TagCount++;
    }

    return TRUE;
}


cmsContext CMSEXPORT cmsGetProfileContextID(cmsHPROFILE hProfile)
{
     _cmsICCPROFILE* Icc = (_cmsICCPROFILE*) hProfile;

    if (Icc == NULL) return NULL;
    return Icc -> ContextID;
}


cmsUInt32Number NULLTell(cmsIOHANDLER* iohandler)
{
    FILENULL* ResData = (FILENULL*) iohandler ->stream;
    return ResData -> Pointer;
}


cmsFloat64Number CMSEXPORT cmsGetProfileVersion(cmsHPROFILE hProfile)
{
    _cmsICCPROFILE*  Icc = (_cmsICCPROFILE*) hProfile;
    cmsUInt32Number n = Icc -> Version >> 16;

    return BaseToBase(n, 16, 10) / 100.0;
}


cmsBool HeaderSection(cmsIT8* it8)
{
    char VarName[MAXID];
    char Buffer[MAXSTR];
    KEYVALUE* Key;

        while (it8->sy != SEOF &&
               it8->sy != SSYNERROR &&
               it8->sy != SBEGIN_DATA_FORMAT &&
               it8->sy != SBEGIN_DATA) {

        switch (it8 -> sy) {

        case SKEYWORD:
                InSymbol(it8);
                if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
                if (!AddAvailableProperty(it8, Buffer, WRITE_UNCOOKED)) return FALSE;
                InSymbol(it8);
                break;

        case SDATA_FORMAT_ID:
                InSymbol(it8);
                if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
                if (!AddAvailableSampleID(it8, Buffer)) return FALSE;
                InSymbol(it8);
                break;

        case SIDENT:
                strncpy(VarName, it8->id, MAXID-1);
                VarName[MAXID-1] = 0;

                if (!IsAvailableOnList(it8-> ValidKeywords, VarName, NULL, &Key)) {

#ifdef CMS_STRICT_CGATS
                 return SynError(it8, "Undefined keyword '%s'", VarName);
#else
                    Key = AddAvailableProperty(it8, VarName, WRITE_UNCOOKED);
                    if (Key == NULL) return FALSE;
#endif
                }

                InSymbol(it8);
                if (!GetVal(it8, Buffer, MAXSTR-1, "Property data expected")) return FALSE;

                if(Key->WriteAs != WRITE_PAIR) {
                    AddToList(it8, &GetTable(it8)->HeaderList, VarName, NULL, Buffer,
                                (it8->sy == SSTRING) ? WRITE_STRINGIFY : WRITE_UNCOOKED);
                }
                else {
                    const char *Subkey;
                    char *Nextkey;
                    if (it8->sy != SSTRING)
                        return SynError(it8, "Invalid value '%s' for property '%s'.", Buffer, VarName);

                    // chop the string as a list of "subkey, value" pairs, using ';' as a separator
                    for (Subkey = Buffer; Subkey != NULL; Subkey = Nextkey)
                    {
                        char *Value, *temp;

                        //  identify token pair boundary
                        Nextkey = (char*) strchr(Subkey, ';');
                        if(Nextkey)
                            *Nextkey++ = '\0';

                        // for each pair, split the subkey and the value
                        Value = (char*) strrchr(Subkey, ',');
                        if(Value == NULL)
                            return SynError(it8, "Invalid value for property '%s'.", VarName);

                        // gobble the spaces before the coma, and the coma itself
                        temp = Value++;
                        do *temp-- = '\0'; while(temp >= Subkey && *temp == ' ');

                        // gobble any space at the right
                        temp = Value + strlen(Value) - 1;
                        while(*temp == ' ') *temp-- = '\0';

                        // trim the strings from the left
                        Subkey += strspn(Subkey, " ");
                        Value += strspn(Value, " ");

                        if(Subkey[0] == 0 || Value[0] == 0)
                            return SynError(it8, "Invalid value for property '%s'.", VarName);
                        AddToList(it8, &GetTable(it8)->HeaderList, VarName, Subkey, Value, WRITE_PAIR);
                    }
                }

                InSymbol(it8);
                break;

        case SEOLN: break;

        default:
                return SynError(it8, "expected keyword or identifier");
        }

    SkipEOLN(it8);
    }

    return TRUE;

}


int CMSEXPORT cmsIT8SetTableByLabel(cmsHANDLE hIT8, const char* cSet, const char* cField, const char* ExpectedType)
{
    const char* cLabelFld;
    char Type[256], Label[256];
    int nTable;

    _cmsAssert(hIT8 != NULL);

    if (cField != NULL && *cField == 0)
            cField = "LABEL";

    if (cField == NULL)
            cField = "LABEL";

    cLabelFld = cmsIT8GetData(hIT8, cSet, cField);
    if (!cLabelFld) return -1;

    if (sscanf(cLabelFld, "%255s %d %255s", Label, &nTable, Type) != 3)
            return -1;

    if (ExpectedType != NULL && *ExpectedType == 0)
        ExpectedType = NULL;

    if (ExpectedType) {

        if (cmsstrcasecmp(Type, ExpectedType) != 0) return -1;
    }

    return cmsIT8SetTable(hIT8, nTable);
}


cmsFloat64Number ParseFloatNumber(const char *Buffer)
{
    cmsFloat64Number dnum = 0.0;
    int sign = 1;

    // keep safe
    if (Buffer == NULL) return 0.0;

    if (*Buffer == '-' || *Buffer == '+') {

         sign = (*Buffer == '-') ? -1 : 1;
         Buffer++;
    }

    while (*Buffer && isdigit((int) *Buffer)) {

        dnum = dnum * 10.0 + (*Buffer - '0');
        if (*Buffer) Buffer++;
    }

    if (*Buffer == '.') {

        cmsFloat64Number frac = 0.0;      // fraction
        int prec = 0;                     // precission

        if (*Buffer) Buffer++;

        while (*Buffer && isdigit((int) *Buffer)) {

            frac = frac * 10.0 + (*Buffer - '0');
            prec++;
            if (*Buffer) Buffer++;
        }

        dnum = dnum + (frac / xpow10(prec));
    }

    // Exponent, example 34.00E+20
    if (*Buffer && toupper(*Buffer) == 'E') {

        int e;
        int sgn;

        if (*Buffer) Buffer++;
        sgn = 1;

        if (*Buffer == '-') {

            sgn = -1;
            if (*Buffer) Buffer++;
        }
        else
            if (*Buffer == '+') {

                sgn = +1;
                if (*Buffer) Buffer++;
            }

            e = 0;
            while (*Buffer && isdigit((int) *Buffer)) {

                if ((cmsFloat64Number) e * 10L < INT_MAX)
                    e = e * 10 + (*Buffer - '0');

                if (*Buffer) Buffer++;
            }

            e = sgn*e;
            dnum = dnum * xpow10(e);
    }

    return sign * dnum;
}


cmsBool ParseIT8(cmsIT8* it8, cmsBool nosheet)
{
    char* SheetTypePtr = it8 ->Tab[0].SheetType;

    if (nosheet == 0) {
        ReadType(it8, SheetTypePtr);
    }

    InSymbol(it8);

    SkipEOLN(it8);

    while (it8-> sy != SEOF &&
           it8-> sy != SSYNERROR) {

            switch (it8 -> sy) {

            case SBEGIN_DATA_FORMAT:
                    if (!DataFormatSection(it8)) return FALSE;
                    break;

            case SBEGIN_DATA:

                    if (!DataSection(it8)) return FALSE;

                    if (it8 -> sy != SEOF) {

                            AllocTable(it8);
                            it8 ->nTable = it8 ->TablesCount - 1;

                            // Read sheet type if present. We only support identifier and string.
                            // <ident> <eoln> is a type string
                            // anything else, is not a type string
                            if (nosheet == 0) {

                                if (it8 ->sy == SIDENT) {

                                    // May be a type sheet or may be a prop value statement. We cannot use insymbol in
                                    // this special case...
                                     while (isseparator(it8->ch))
                                         NextCh(it8);

                                     // If a newline is found, then this is a type string
                                    if (it8 ->ch == '\n') {

                                         cmsIT8SetSheetType(it8, it8 ->id);
                                         InSymbol(it8);
                                    }
                                    else
                                    {
                                        // It is not. Just continue
                                        cmsIT8SetSheetType(it8, "");
                                    }
                                }
                                else
                                    // Validate quoted strings
                                    if (it8 ->sy == SSTRING) {
                                        cmsIT8SetSheetType(it8, it8 ->str);
                                        InSymbol(it8);
                                    }
                           }

                    }
                    break;

            case SEOLN:
                    SkipEOLN(it8);
                    break;

            default:
                    if (!HeaderSection(it8)) return FALSE;
           }

    }

    return (it8 -> sy != SSYNERROR);
}


cmsHTRANSFORM CreateRoundtripXForm(cmsHPROFILE hProfile, cmsUInt32Number nIntent)
{
    cmsHPROFILE hLab = cmsCreateLab4Profile(NULL);
    cmsHTRANSFORM xform;
    cmsBool BPC[4] = { FALSE, FALSE, FALSE, FALSE };
    cmsFloat64Number States[4] = { 1.0, 1.0, 1.0, 1.0 };
    cmsHPROFILE hProfiles[4];
    cmsUInt32Number Intents[4];
    cmsContext ContextID = cmsGetProfileContextID(hProfile);

    hProfiles[0] = hLab; hProfiles[1] = hProfile; hProfiles[2] = hProfile; hProfiles[3] = hLab;
    Intents[0]   = INTENT_RELATIVE_COLORIMETRIC; Intents[1] = nIntent; Intents[2] = INTENT_RELATIVE_COLORIMETRIC; Intents[3] = INTENT_RELATIVE_COLORIMETRIC;

    xform =  cmsCreateExtendedTransform(ContextID, 4, hProfiles, BPC, Intents,
        States, NULL, 0, TYPE_Lab_DBL, TYPE_Lab_DBL, cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);

    cmsCloseProfile(hLab);
    return xform;
}


void *Type_LUT8_Read(struct _cms_typehandler_struct* self, cmsIOHANDLER* io, cmsUInt32Number* nItems, cmsUInt32Number SizeOfTag)
{
    cmsUInt8Number InputChannels, OutputChannels, CLUTpoints;
    cmsUInt8Number* Temp = NULL;
    cmsPipeline* NewLUT = NULL;
    cmsStage *mpemat, *mpeclut;
    cmsUInt32Number nTabSize, i;
    cmsFloat64Number Matrix[3*3];

    *nItems = 0;

    if (!_cmsReadUInt8Number(io, &InputChannels)) goto Error;
    if (!_cmsReadUInt8Number(io, &OutputChannels)) goto Error;
    if (!_cmsReadUInt8Number(io, &CLUTpoints)) goto Error;

     if (CLUTpoints == 1) goto Error; // Impossible value, 0 for no CLUT and then 2 at least

    // Padding
    if (!_cmsReadUInt8Number(io, NULL)) goto Error;

    // Do some checking

    if (InputChannels > cmsMAXCHANNELS)  goto Error;
    if (OutputChannels > cmsMAXCHANNELS) goto Error;

   // Allocates an empty Pipeline
    NewLUT = cmsPipelineAlloc(self ->ContextID, InputChannels, OutputChannels);
    if (NewLUT == NULL) goto Error;

    // Read the Matrix
    if (!_cmsRead15Fixed16Number(io,  &Matrix[0])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[1])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[2])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[3])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[4])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[5])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[6])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[7])) goto Error;
    if (!_cmsRead15Fixed16Number(io,  &Matrix[8])) goto Error;

    // Only operates if not identity...
    if ((InputChannels == 3) && !_cmsMAT3isIdentity((cmsMAT3*) Matrix)) {

        mpemat = cmsStageAllocMatrix(self ->ContextID, 3, 3, Matrix, NULL);
        if (mpemat == NULL) goto Error;
        cmsPipelineInsertStage(NewLUT, cmsAT_BEGIN, mpemat);
    }

    // Get input tables
    if (!Read8bitTables(self ->ContextID, io,  NewLUT, InputChannels)) goto Error;

    // Get 3D CLUT. Check the overflow....
    nTabSize = uipow(OutputChannels, CLUTpoints, InputChannels);
    if (nTabSize == (size_t) -1) goto Error;
    if (nTabSize > 0) {

        cmsUInt16Number *PtrW, *T;
        cmsUInt32Number Tsize;

        Tsize = (cmsUInt32Number) nTabSize * sizeof(cmsUInt16Number);

        PtrW = T  = (cmsUInt16Number*) _cmsCalloc(self ->ContextID, nTabSize, sizeof(cmsUInt16Number));
        if (T  == NULL) goto Error;

        Temp = (cmsUInt8Number*) _cmsMalloc(self ->ContextID, nTabSize);
        if (Temp == NULL) goto Error;

        if (io ->Read(io, Temp, nTabSize, 1) != 1) goto Error;

        for (i = 0; i < nTabSize; i++) {

            *PtrW++ = FROM_8_TO_16(Temp[i]);
        }
        _cmsFree(self ->ContextID, Temp);
        Temp = NULL;

        mpeclut = cmsStageAllocCLut16bit(self ->ContextID, CLUTpoints, InputChannels, OutputChannels, T);
        if (mpeclut == NULL) goto Error;
        cmsPipelineInsertStage(NewLUT, cmsAT_END, mpeclut);
        _cmsFree(self ->ContextID, T);
    }

    // Get output tables
    if (!Read8bitTables(self ->ContextID, io,  NewLUT, OutputChannels)) goto Error;

    *nItems = 1;
    return NewLUT;

Error:
    if (NewLUT != NULL) cmsPipelineFree(NewLUT);
    return NULL;

    cmsUNUSED_PARAMETER(SizeOfTag);
}


void PrelinEval16(register const cmsUInt16Number Input[],
                  register cmsUInt16Number Output[],
                  register const void* D)
{
    Prelin16Data* p16 = (Prelin16Data*) D;
    cmsUInt16Number  StageABC[MAX_INPUT_DIMENSIONS];
    int i;

    for (i=0; i < p16 ->nInputs; i++) {

        p16 ->EvalCurveIn16[i](&Input[i], &StageABC[i], p16 ->ParamsCurveIn16[i]);
    }

    p16 ->EvalCLUT(StageABC, p16 ->StageDEF, p16 ->CLUTparams);

    for (i=0; i < p16 ->nOutputs; i++) {

        p16 ->EvalCurveOut16[i](&p16->StageDEF[i], &Output[i], p16 ->ParamsCurveOut16[i]);
    }
}


cmsBool CMSEXPORT cmsDetectBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
{

    // Zero for black point
    if (cmsGetDeviceClass(hProfile) == cmsSigLinkClass) {

        BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0;
        return FALSE;
    }

    // v4 + perceptual & saturation intents does have its own black point, and it is
    // well specified enough to use it. Black point tag is deprecated in V4.
    if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) &&
        (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) {

            // Matrix shaper share MRC & perceptual intents
            if (cmsIsMatrixShaper(hProfile))
                return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0);

            // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents
            BlackPoint -> X = cmsPERCEPTUAL_BLACK_X;
            BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y;
            BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z;

            return TRUE;
    }

#ifdef CMS_USE_PROFILE_BLACK_POINT_TAG

    // v2, v4 rel/abs colorimetric
    if (cmsIsTag(hProfile, cmsSigMediaBlackPointTag) &&
        Intent == INTENT_RELATIVE_COLORIMETRIC) {

            cmsCIEXYZ *BlackPtr, BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite;
            cmsCIELab Lab;

            // If black point is specified, then use it,

            BlackPtr = cmsReadTag(hProfile, cmsSigMediaBlackPointTag);
            if (BlackPtr != NULL) {

                BlackXYZ = *BlackPtr;
                _cmsReadMediaWhitePoint(&MediaWhite, hProfile);

                // Black point is absolute XYZ, so adapt to D50 to get PCS value
                cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ);

                // Force a=b=0 to get rid of any chroma
                cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint);
                Lab.a = Lab.b = 0;
                if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50
                cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab);

                if (BlackPoint != NULL)
                    *BlackPoint = TrustedBlackPoint;

                return TRUE;
            }
    }
#endif

    // That is about v2 profiles.

    // If output profile, discount ink-limiting and that's all
    if (Intent == INTENT_RELATIVE_COLORIMETRIC &&
        (cmsGetDeviceClass(hProfile) == cmsSigOutputClass) &&
        (cmsGetColorSpace(hProfile)  == cmsSigCmykData))
        return BlackPointUsingPerceptualBlack(BlackPoint, hProfile);

    // Nope, compute BP using current intent.
    return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags);
}


	void throwUploadBufferingException(request_rec *r, int code) {
		DirConfig *config = getDirConfig(r);
		string message("An error occured while "
			"buffering HTTP upload data to "
			"a temporary file in ");
		ServerInstanceDir::GenerationPtr generation = agentsStarter.getGeneration();
		message.append(config->getUploadBufferDir(generation));

		switch (code) {
		case ENOSPC:
			message.append(". Disk directory doesn't have enough disk space, "
				"so please make sure that it has "
				"enough disk space for buffering file uploads, "
				"or set the 'PassengerUploadBufferDir' directive "
				"to a directory that has enough disk space.");
			throw RuntimeException(message);
			break;
		case EDQUOT:
			message.append(". The current Apache worker process (which is "
				"running as ");
			message.append(getProcessUsername());
			message.append(") cannot write to this directory because of "
				"disk quota limits. Please make sure that the volume "
				"that this directory resides on has enough disk space "
				"quota for the Apache worker process, or set the "
				"'PassengerUploadBufferDir' directive to a different "
				"directory that has enough disk space quota.");
			throw RuntimeException(message);
			break;
		case ENOENT:
			message.append(". This directory doesn't exist, so please make "
				"sure that this directory exists, or set the "
				"'PassengerUploadBufferDir' directive to a "
				"directory that exists and can be written to.");
			throw RuntimeException(message);
			break;
		case EACCES:
			message.append(". The current Apache worker process (which is "
				"running as ");
			message.append(getProcessUsername());
			message.append(") doesn't have permissions to write to this "
				"directory. Please change the permissions for this "
				"directory (as well as all parent directories) so that "
				"it is writable by the Apache worker process, or set "
				"the 'PassengerUploadBufferDir' directive to a directory "
				"that Apache can write to.");
			throw RuntimeException(message);
			break;
		default:
			throw SystemException(message, code);
			break;
		}
	}


	int undoRedirectionToDispatchCgi(request_rec *r) {
		RequestNote *note = getRequestNote(r);
		if (note == 0 || !hasModRewrite()) {
			return DECLINED;
		}

		if (r->handler != NULL && strcmp(r->handler, "redirect-handler") == 0) {
			// Check whether r->filename looks like "redirect:.../dispatch.(f)cgi"
			size_t len = strlen(r->filename);
			// 22 == strlen("redirect:/dispatch.cgi")
			if (len >= 22 && memcmp(r->filename, "redirect:", 9) == 0
			 && (memcmp(r->filename + len - 13, "/dispatch.cgi", 13) == 0
			  || memcmp(r->filename + len - 14, "/dispatch.fcgi", 14) == 0)) {
				if (note->filenameBeforeModRewrite != NULL) {
					r->filename = note->filenameBeforeModRewrite;
					r->canonical_filename = note->filenameBeforeModRewrite;
					r->handler = note->handlerBeforeModRewrite;
				}
			}
		}
		return DECLINED;
	}


	bool hasModDir() {
		if (m_hasModDir == UNKNOWN) {
			if (ap_find_linked_module("mod_dir.c")) {
				m_hasModDir = YES;
			} else {
				m_hasModDir = NO;
			}
		}
		return m_hasModDir == YES;
	}


destroy_hooks(void *arg) {
	try {
		this_thread::disable_interruption di;
		this_thread::disable_syscall_interruption dsi;
		P_DEBUG("Shutting down Phusion Passenger...");
		delete hooks;
		hooks = NULL;
	} catch (const thread_interrupted &) {
		// Ignore interruptions, we're shutting down anyway.
		P_TRACE(3, "A system call was interrupted during shutdown of mod_passenger.");
	} catch (const std::exception &e) {
		// Ignore other exceptions, we're shutting down anyway.
		P_TRACE(3, "Exception during shutdown of mod_passenger: " << e.what());
	}
	return APR_SUCCESS;
}


	int startBlockingModDir(request_rec *r) {
		RequestNote *note = getRequestNote(r);
		if (note != 0 && hasModDir()) {
			note->oldFileType = r->finfo.filetype;
			r->finfo.filetype = APR_NOFILE;
		}
		return DECLINED;
	}


	int handleRequestWhenInHighPerformanceMode(request_rec *r) {
		DirConfig *config = getDirConfig(r);
		if (config->highPerformanceMode()) {
			return handleRequest(r);
		} else {
			return DECLINED;
		}
	}


findUnionStationGatewayCert(const ResourceLocator &locator,
	const string &cert)
{
	if (cert.empty()) {
		return locator.getResourcesDir() + "/union_station_gateway.crt";
	} else if (cert != "-") {
		return cert;
	} else {
		return "";
	}
}


	void childInit(apr_pool_t *pchild, server_rec *s) {
		agentsStarter.detach();
	}


static int ps_mm_key_exists(ps_mm *data, const char *key TSRMLS_DC)
{
	ps_sd *sd;

	if (!key) {
		return FAILURE;
	}
	sd = ps_sd_lookup(data, key, 0);
	if (sd) {
		return SUCCESS;
	}
	return FAILURE;
}


static char *ps_files_path_create(char *buf, size_t buflen, ps_files *data, const char *key)
{
	size_t key_len;
	const char *p;
	int i;
	int n;

	key_len = strlen(key);
	if (key_len <= data->dirdepth ||
		buflen < (strlen(data->basedir) + 2 * data->dirdepth + key_len + 5 + sizeof(FILE_PREFIX))) {
		return NULL;
	}

	p = key;
	memcpy(buf, data->basedir, data->basedir_len);
	n = data->basedir_len;
	buf[n++] = PHP_DIR_SEPARATOR;
	for (i = 0; i < (int)data->dirdepth; i++) {
		buf[n++] = *p++;
		buf[n++] = PHP_DIR_SEPARATOR;
	}
	memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
	n += sizeof(FILE_PREFIX) - 1;
	memcpy(buf + n, key, key_len);
	n += key_len;
	buf[n] = '\0';

	return buf;
}


static void php_session_initialize(TSRMLS_D) /* {{{ */
{
	char *val = NULL;
	int vallen;

	if (!PS(mod)) {
		php_error_docref(NULL TSRMLS_CC, E_ERROR, "No storage module chosen - failed to initialize session");
		return;
	}

	/* Open session handler first */
	if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name) TSRMLS_CC) == FAILURE) {
		php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path));
		return;
	}

	/* If there is no ID, use session module to create one */
	if (!PS(id)) {
		PS(id) = PS(mod)->s_create_sid(&PS(mod_data), NULL TSRMLS_CC);
		if (!PS(id)) {
			php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to create session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
			return;
		}
		if (PS(use_cookies)) {
			PS(send_cookie) = 1;
		}
	}

	php_session_reset_id(TSRMLS_C);
	PS(session_status) = php_session_active;

	/* Read data */
	php_session_track_init(TSRMLS_C);
	if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, &vallen TSRMLS_CC) == FAILURE) {
		/* Some broken save handler implementation returns FAILURE for non-existent session ID */
		/* It's better to rase error for this, but disabled error for better compatibility */
		/*
		php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Failed to read session data: %s (path: %s)", PS(mod)->s_name, PS(save_path));
		*/
	}
	if (val) {
		php_session_decode(val, vallen TSRMLS_CC);
		efree(val);
	}

	if (!PS(use_cookies) && PS(send_cookie)) {
		if (PS(use_trans_sid) && !PS(use_only_cookies)) {
			PS(apply_trans_sid) = 1;
		}
		PS(send_cookie) = 0;
	}
}


PHP_MINIT_FUNCTION(ps_mm)
{
	int save_path_len = strlen(PS(save_path));
	int mod_name_len = strlen(sapi_module.name);
	int euid_len;
	char *ps_mm_path, euid[30];
	int ret;

	ps_mm_instance = calloc(sizeof(*ps_mm_instance), 1);
	if (!ps_mm_instance) {
		return FAILURE;
	}

	if (!(euid_len = slprintf(euid, sizeof(euid), "%d", geteuid()))) {
		free(ps_mm_instance);
		ps_mm_instance = NULL;
		return FAILURE;
	}

	/* Directory + '/' + File + Module Name + Effective UID + \0 */
	ps_mm_path = emalloc(save_path_len + 1 + (sizeof(PS_MM_FILE) - 1) + mod_name_len + euid_len + 1);

	memcpy(ps_mm_path, PS(save_path), save_path_len);
	if (save_path_len && PS(save_path)[save_path_len - 1] != DEFAULT_SLASH) {
		ps_mm_path[save_path_len] = DEFAULT_SLASH;
		save_path_len++;
	}
	memcpy(ps_mm_path + save_path_len, PS_MM_FILE, sizeof(PS_MM_FILE) - 1);
	save_path_len += sizeof(PS_MM_FILE) - 1;
	memcpy(ps_mm_path + save_path_len, sapi_module.name, mod_name_len);
	save_path_len += mod_name_len;
	memcpy(ps_mm_path + save_path_len, euid, euid_len);
	ps_mm_path[save_path_len + euid_len] = '\0';

	ret = ps_mm_initialize(ps_mm_instance, ps_mm_path);

	efree(ps_mm_path);

	if (ret != SUCCESS) {
		free(ps_mm_instance);
		ps_mm_instance = NULL;
		return FAILURE;
	}

	php_session_register_module(&ps_mod_mm);
	return SUCCESS;
}


PHP_FUNCTION(openssl_pkcs12_export_to_file)
{
	X509 * cert = NULL;
	BIO * bio_out = NULL;
	PKCS12 * p12 = NULL;
	char * filename;
	char * friendly_name = NULL;
	int filename_len;
	char * pass;
	int pass_len;
	zval **zcert = NULL, *zpkey = NULL, *args = NULL;
	EVP_PKEY *priv_key = NULL;
	long certresource, keyresource;
	zval ** item;
	STACK_OF(X509) *ca = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Zpzs|a", &zcert, &filename, &filename_len, &zpkey, &pass, &pass_len, &args) == FAILURE)
		return;

	RETVAL_FALSE;

	cert = php_openssl_x509_from_zval(zcert, 0, &certresource TSRMLS_CC);
	if (cert == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot get cert from parameter 1");
		return;
	}
	priv_key = php_openssl_evp_from_zval(&zpkey, 0, "", 1, &keyresource TSRMLS_CC);
	if (priv_key == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot get private key from parameter 3");
		goto cleanup;
	}
	if (cert && !X509_check_private_key(cert, priv_key)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "private key does not correspond to cert");
		goto cleanup;
	}
	if (php_openssl_open_base_dir_chk(filename TSRMLS_CC)) {
		goto cleanup;
	}

	/* parse extra config from args array, promote this to an extra function */
	if (args && zend_hash_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name"), (void**)&item) == SUCCESS)
		friendly_name = Z_STRVAL_PP(item);
	/* certpbe (default RC2-40)
	   keypbe (default 3DES)
	   friendly_caname
	*/

	if (args && zend_hash_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts"), (void**)&item) == SUCCESS)
		ca = php_array_to_X509_sk(item TSRMLS_CC);
	/* end parse extra config */

	/*PKCS12 *PKCS12_create(char *pass, char *name, EVP_PKEY *pkey, X509 *cert, STACK_OF(X509) *ca,
                                       int nid_key, int nid_cert, int iter, int mac_iter, int keytype);*/

	p12 = PKCS12_create(pass, friendly_name, priv_key, cert, ca, 0, 0, 0, 0, 0);

	bio_out = BIO_new_file(filename, "w");
	if (bio_out) {

		i2d_PKCS12_bio(bio_out, p12);

		RETVAL_TRUE;
	} else {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "error opening file %s", filename);
	}

	BIO_free(bio_out);
	PKCS12_free(p12);
	php_sk_X509_free(ca);

cleanup:

	if (keyresource == -1 && priv_key) {
		EVP_PKEY_free(priv_key);
	}
	if (certresource == -1 && cert) {
		X509_free(cert);
	}
}


PHP_FUNCTION(openssl_pkey_get_public)
{
	zval **cert;
	EVP_PKEY *pkey;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &cert) == FAILURE) {
		return;
	}
	Z_TYPE_P(return_value) = IS_RESOURCE;
	pkey = php_openssl_evp_from_zval(cert, 1, NULL, 1, &Z_LVAL_P(return_value) TSRMLS_CC);

	if (pkey == NULL) {
		RETURN_FALSE;
	}
	zend_list_addref(Z_LVAL_P(return_value));
}


PHP_FUNCTION(openssl_pkcs7_encrypt)
{
	zval ** zrecipcerts, * zheaders = NULL;
	STACK_OF(X509) * recipcerts = NULL;
	BIO * infile = NULL, * outfile = NULL;
	long flags = 0;
	PKCS7 * p7 = NULL;
	HashPosition hpos;
	zval ** zcertval;
	X509 * cert;
	const EVP_CIPHER *cipher = NULL;
	long cipherid = PHP_OPENSSL_CIPHER_DEFAULT;
	uint strindexlen;
	ulong intindex;
	char * strindex;
	char * infilename = NULL;	int infilename_len;
	char * outfilename = NULL;	int outfilename_len;

	RETVAL_FALSE;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ppZa!|ll", &infilename, &infilename_len,
				&outfilename, &outfilename_len, &zrecipcerts, &zheaders, &flags, &cipherid) == FAILURE)
		return;

	if (php_openssl_open_base_dir_chk(infilename TSRMLS_CC) || php_openssl_open_base_dir_chk(outfilename TSRMLS_CC)) {
		return;
	}

	infile = BIO_new_file(infilename, "r");
	if (infile == NULL) {
		goto clean_exit;
	}

	outfile = BIO_new_file(outfilename, "w");
	if (outfile == NULL) {
		goto clean_exit;
	}

	recipcerts = sk_X509_new_null();

	/* get certs */
	if (Z_TYPE_PP(zrecipcerts) == IS_ARRAY) {
		zend_hash_internal_pointer_reset_ex(HASH_OF(*zrecipcerts), &hpos);
		while(zend_hash_get_current_data_ex(HASH_OF(*zrecipcerts), (void**)&zcertval, &hpos) == SUCCESS) {
			long certresource;

			cert = php_openssl_x509_from_zval(zcertval, 0, &certresource TSRMLS_CC);
			if (cert == NULL) {
				goto clean_exit;
			}

			if (certresource != -1) {
				/* we shouldn't free this particular cert, as it is a resource.
					make a copy and push that on the stack instead */
				cert = X509_dup(cert);
				if (cert == NULL) {
					goto clean_exit;
				}
			}
			sk_X509_push(recipcerts, cert);

			zend_hash_move_forward_ex(HASH_OF(*zrecipcerts), &hpos);
		}
	} else {
		/* a single certificate */
		long certresource;

		cert = php_openssl_x509_from_zval(zrecipcerts, 0, &certresource TSRMLS_CC);
		if (cert == NULL) {
			goto clean_exit;
		}

		if (certresource != -1) {
			/* we shouldn't free this particular cert, as it is a resource.
				make a copy and push that on the stack instead */
			cert = X509_dup(cert);
			if (cert == NULL) {
				goto clean_exit;
			}
		}
		sk_X509_push(recipcerts, cert);
	}

	/* sanity check the cipher */
	cipher = php_openssl_get_evp_cipher_from_algo(cipherid);
	if (cipher == NULL) {
		/* shouldn't happen */
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to get cipher");
		goto clean_exit;
	}

	p7 = PKCS7_encrypt(recipcerts, infile, (EVP_CIPHER*)cipher, flags);

	if (p7 == NULL) {
		goto clean_exit;
	}

	/* tack on extra headers */
	if (zheaders) {
		zend_hash_internal_pointer_reset_ex(HASH_OF(zheaders), &hpos);
		while(zend_hash_get_current_data_ex(HASH_OF(zheaders), (void**)&zcertval, &hpos) == SUCCESS) {
			strindex = NULL;
			zend_hash_get_current_key_ex(HASH_OF(zheaders), &strindex, &strindexlen, &intindex, 0, &hpos);

			convert_to_string_ex(zcertval);

			if (strindex) {
				BIO_printf(outfile, "%s: %s\n", strindex, Z_STRVAL_PP(zcertval));
			} else {
				BIO_printf(outfile, "%s\n", Z_STRVAL_PP(zcertval));
			}

			zend_hash_move_forward_ex(HASH_OF(zheaders), &hpos);
		}
	}

	(void)BIO_reset(infile);

	/* write the encrypted data */
	SMIME_write_PKCS7(outfile, p7, infile, flags);

	RETVAL_TRUE;

clean_exit:
	PKCS7_free(p7);
	BIO_free(infile);
	BIO_free(outfile);
	if (recipcerts) {
		sk_X509_pop_free(recipcerts, X509_free);
	}
}


void CLASS bad_pixels (const char *cfname)
{
  FILE *fp=NULL;
#ifndef LIBRAW_LIBRARY_BUILD
  char *fname, *cp, line[128];
  int len, time, row, col, r, c, rad, tot, n, fixed=0;
#else
  char *cp, line[128];
  int time, row, col, r, c, rad, tot, n;
#ifdef DCRAW_VERBOSE
  int fixed = 0;
#endif
#endif

  if (!filters) return;
#ifdef LIBRAW_LIBRARY_BUILD
  RUN_CALLBACK(LIBRAW_PROGRESS_BAD_PIXELS,0,2);
#endif
  if (cfname)
    fp = fopen (cfname, "r");
#line 4141 "dcraw/dcraw.c"
  if (!fp)
      {
#ifdef LIBRAW_LIBRARY_BUILD
          imgdata.process_warnings |= LIBRAW_WARN_NO_BADPIXELMAP;
#endif
          return;
      }
  while (fgets (line, 128, fp)) {
    cp = strchr (line, '#');
    if (cp) *cp = 0;
    if (sscanf (line, "%d %d %d", &col, &row, &time) != 3) continue;
    if ((unsigned) col >= width || (unsigned) row >= height) continue;
    if (time > timestamp) continue;
    for (tot=n=0, rad=1; rad < 3 && n==0; rad++)
      for (r = row-rad; r <= row+rad; r++)
	for (c = col-rad; c <= col+rad; c++)
	  if ((unsigned) r < height && (unsigned) c < width &&
		(r != row || c != col) && fcol(r,c) == fcol(row,col)) {
	    tot += BAYER2(r,c);
	    n++;
	  }
    BAYER2(row,col) = tot/n;
#ifdef DCRAW_VERBOSE
    if (verbose) {
      if (!fixed++)
	fprintf (stderr,_("Fixed dead pixels at:"));
      fprintf (stderr, " %d,%d", col, row);
    }
#endif
  }
#ifdef DCRAW_VERBOSE
  if (fixed) fputc ('\n', stderr);
#endif
  fclose (fp);
#ifdef LIBRAW_LIBRARY_BUILD
  RUN_CALLBACK(LIBRAW_PROGRESS_BAD_PIXELS,1,2);
#endif
}


void CLASS fuji_rotate()
{
  int i, row, col;
  double step;
  float r, c, fr, fc;
  unsigned ur, uc;
  ushort wide, high, (*img)[4], (*pix)[4];

  if (!fuji_width) return;
#ifdef DCRAW_VERBOSE
  if (verbose)
    fprintf (stderr,_("Rotating image 45 degrees...\n"));
#endif
  fuji_width = (fuji_width - 1 + shrink) >> shrink;
  step = sqrt(0.5);
  wide = fuji_width / step;
  high = (height - fuji_width) / step;
  img = (ushort (*)[4]) calloc (wide*high, sizeof *img);
  merror (img, "fuji_rotate()");

#ifdef LIBRAW_LIBRARY_BUILD
  RUN_CALLBACK(LIBRAW_PROGRESS_FUJI_ROTATE,0,2);
#endif

  for (row=0; row < high; row++)
    for (col=0; col < wide; col++) {
      ur = r = fuji_width + (row-col)*step;
      uc = c = (row+col)*step;
      if (ur > height-2 || uc > width-2) continue;
      fr = r - ur;
      fc = c - uc;
      pix = image + ur*width + uc;
      for (i=0; i < colors; i++)
	img[row*wide+col][i] =
	  (pix[    0][i]*(1-fc) + pix[      1][i]*fc) * (1-fr) +
	  (pix[width][i]*(1-fc) + pix[width+1][i]*fc) * fr;
    }
  free (image);
  width  = wide;
  height = high;
  image  = img;
  fuji_width = 0;
#ifdef LIBRAW_LIBRARY_BUILD
  RUN_CALLBACK(LIBRAW_PROGRESS_FUJI_ROTATE,1,2);
#endif
}


int LibRaw_file_datastream::seek(INT64 o, int whence)
{
    if(substream) return substream->seek(o,whence);
    LR_STREAM_CHK();
    std::ios_base::seekdir dir;
    switch (whence)
        {
        case SEEK_SET: dir = std::ios_base::beg; break;
        case SEEK_CUR: dir = std::ios_base::cur; break;
        case SEEK_END: dir = std::ios_base::end; break;
        default: dir = std::ios_base::beg;
        }
    return (int)f->pubseekoff((long)o, dir);
}


void CLASS bad_pixels (const char *cfname)
{
  FILE *fp=0;
#ifndef LIBRAW_LIBRARY_BUILD
  char *fname, *cp, line[128];
  int len, time, row, col, r, c, rad, tot, n, fixed=0;
#else
  char *cp, line[128];
  int time, row, col, r, c, rad, tot, n;
#ifdef DCRAW_VERBOSE
  int fixed = 0;
#endif
#endif

  if (!filters) return;
#ifdef LIBRAW_LIBRARY_BUILD
  RUN_CALLBACK(LIBRAW_PROGRESS_BAD_PIXELS,0,2);
#endif
  if (cfname)
    fp = fopen (cfname, "r");
// @end FILEIO
  else {
    for (len=32 ; ; len *= 2) {
      fname = (char *) malloc (len);
      if (!fname) return;
      if (getcwd (fname, len-16)) break;
      free (fname);
      if (errno != ERANGE) return;
    }
#if defined(WIN32) || defined(DJGPP)
    if (fname[1] == ':')
      memmove (fname, fname+2, len-2);
    for (cp=fname; *cp; cp++)
      if (*cp == '\\') *cp = '/';
#endif
    cp = fname + strlen(fname);
    if (cp[-1] == '/') cp--;
    while (*fname == '/') {
      strcpy (cp, "/.badpixels");
      if ((fp = fopen (fname, "r"))) break;
      if (cp == fname) break;
      while (*--cp != '/');
    }
    free (fname);
  }
// @out FILEIO
  if (!fp)
      {
#ifdef LIBRAW_LIBRARY_BUILD
          imgdata.process_warnings |= LIBRAW_WARN_NO_BADPIXELMAP;
#endif
          return;
      }
  while (fgets (line, 128, fp)) {
    cp = strchr (line, '#');
    if (cp) *cp = 0;
    if (sscanf (line, "%d %d %d", &col, &row, &time) != 3) continue;
    if ((unsigned) col >= width || (unsigned) row >= height) continue;
    if (time > timestamp) continue;
    for (tot=n=0, rad=1; rad < 3 && n==0; rad++)
      for (r = row-rad; r <= row+rad; r++)
	for (c = col-rad; c <= col+rad; c++)
	  if ((unsigned) r < height && (unsigned) c < width &&
		(r != row || c != col) && fc(r,c) == fc(row,col)) {
	    tot += BAYER2(r,c);
	    n++;
	  }
    BAYER2(row,col) = tot/n;
#ifdef DCRAW_VERBOSE
    if (verbose) {
      if (!fixed++)
	fprintf (stderr,_("Fixed dead pixels at:"));
      fprintf (stderr, " %d,%d", col, row);
    }
#endif
  }
#ifdef DCRAW_VERBOSE
  if (fixed) fputc ('\n', stderr);
#endif
  fclose (fp);
#ifdef LIBRAW_LIBRARY_BUILD
  RUN_CALLBACK(LIBRAW_PROGRESS_BAD_PIXELS,1,2);
#endif
}


void CLASS parse_external_jpeg()
{
  const char *file, *ext;
  char *jname, *jfile, *jext;
#ifndef LIBRAW_LIBRARY_BUILD
  FILE *save=ifp;
#else
  if(!ifp->fname())
      {
          imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA ;
          return;
      }
#endif

  ext  = strrchr (ifname, '.');
  file = strrchr (ifname, '/');
  if (!file) file = strrchr (ifname, '\\');
#ifndef LIBRAW_LIBRARY_BUILD
  if (!file) file = ifname-1;
#else
  if (!file) file = (char*)ifname-1;
#endif
  file++;
  if (!ext || strlen(ext) != 4 || ext-file != 8) return;
  jname = (char *) malloc (strlen(ifname) + 1);
  merror (jname, "parse_external_jpeg()");
  strcpy (jname, ifname);
  jfile = file - ifname + jname;
  jext  = ext  - ifname + jname;
  if (strcasecmp (ext, ".jpg")) {
    strcpy (jext, isupper(ext[1]) ? ".JPG":".jpg");
    if (isdigit(*file)) {
      memcpy (jfile, file+4, 4);
      memcpy (jfile+4, file, 4);
    }
  } else
    while (isdigit(*--jext)) {
      if (*jext != '9') {
	(*jext)++;
	break;
      }
      *jext = '0';
    }
#ifndef LIBRAW_LIBRARY_BUILD
  if (strcmp (jname, ifname)) {
    if ((ifp = fopen (jname, "rb"))) {
#ifdef DCRAW_VERBOSE
      if (verbose)
	fprintf (stderr,_("Reading metadata from %s ...\n"), jname);
#endif
      parse_tiff (12);
      thumb_offset = 0;
      is_raw = 1;
      fclose (ifp);
    }
  }
#else
  if (strcmp (jname, ifname))
      {
          if(!ifp->subfile_open(jname))
              {
                  parse_tiff (12);
                  thumb_offset = 0;
                  is_raw = 1;
                  ifp->subfile_close();
              }
          else
              imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA ;
      }
#endif
  if (!timestamp)
      {
#ifdef LIBRAW_LIBRARY_BUILD
          imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA ;
#endif
#ifdef DCRAW_VERBOSE
          fprintf (stderr,_("Failed to read metadata from %s\n"), jname);
#endif
      }
  free (jname);
#ifndef LIBRAW_LIBRARY_BUILD
  ifp = save;
#endif
}


void CLASS adobe_dng_load_raw_lj()
{
  unsigned save, trow=0, tcol=0, jwide, jrow, jcol, row, col;
  struct jhead jh;
  ushort *rp;
  while (trow < raw_height) {
    save = ftell(ifp);
    if (tile_length < INT_MAX)
      fseek (ifp, get4(), SEEK_SET);
    if (!ljpeg_start (&jh, 0)) break;
    jwide = jh.wide;
    if (filters) jwide *= jh.clrs;
    jwide /= is_raw;
#ifdef LIBRAW_LIBRARY_BUILD
    if(!data_size)
        throw LIBRAW_EXCEPTION_IO_BADFILE;
    LibRaw_byte_buffer *buf = ifp->make_byte_buffer(data_size);
    LibRaw_bit_buffer bits;
#endif
    for (row=col=jrow=0; jrow < jh.high; jrow++) {
#ifdef LIBRAW_LIBRARY_BUILD
        rp = ljpeg_row_new (jrow, &jh,bits,buf);
#else
        rp = ljpeg_row (jrow, &jh);
#endif
      for (jcol=0; jcol < jwide; jcol++) {
	adobe_copy_pixel (trow+row, tcol+col, &rp);
	if (++col >= tile_width || col >= raw_width)
	  row += 1 + (col = 0);
      }
    }
    fseek (ifp, save+4, SEEK_SET);
    if ((tcol += tile_width) >= raw_width)
      trow += tile_length + (tcol = 0);
    ljpeg_end (&jh);
#ifdef LIBRAW_LIBRARY_BUILD
    delete buf;
#endif
  }
}


void CLASS lossless_jpeg_load_raw()
{
  int jwide, jrow, jcol, val, c, i, row=0, col=0;
#ifndef LIBRAW_LIBRARY_BUILD
  int jidx,j;
#endif
  struct jhead jh;
  int min=INT_MAX;
  ushort *rp;
#ifdef LIBRAW_LIBRARY_BUILD
  int save_min = 0;
  unsigned slicesW[16],slicesWcnt=0,slices;
  unsigned *offset;
  unsigned t_y=0,t_x=0,t_s=0,slice=0,pixelsInSlice,pixno;
  if (!strcasecmp(make,"KODAK"))
      save_min = 1;
#endif

#ifdef LIBRAW_LIBRARY_BUILD
  if (cr2_slice[0]>15)
      throw LIBRAW_EXCEPTION_IO_EOF; // change many slices
#else
  if (cr2_slice[0]>15)
  {
      fprintf(stderr,"Too many CR2 slices: %d\n",cr2_slice[0]+1);
      return;
  }
#endif

  if (!ljpeg_start (&jh, 0)) return;
#ifdef LIBRAW_LIBRARY_BUILD
  if(jh.wide<1 || jh.high<1 || jh.clrs<1 || jh.bits <1)
    throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
  jwide = jh.wide * jh.clrs;

#ifdef LIBRAW_LIBRARY_BUILD
  if(cr2_slice[0])
      {
          for(i=0;i<cr2_slice[0];i++)
              slicesW[slicesWcnt++] = cr2_slice[1];
          slicesW[slicesWcnt++] = cr2_slice[2];
      }
  else
      {
          // not sliced
          slicesW[slicesWcnt++] = raw_width; // safe fallback
      }

  slices = slicesWcnt * jh.high;
  if(!slices)
    throw LIBRAW_EXCEPTION_IO_CORRUPT;
  offset = (unsigned*)calloc(slices+1,sizeof(offset[0]));

  for(slice=0;slice<slices;slice++)
      {
          offset[slice] = (t_x + t_y * raw_width)| (t_s<<28);
          if((offset[slice] & 0x0fffffff) >= raw_width * raw_height)
            {
              free(offset);
              throw LIBRAW_EXCEPTION_IO_BADFILE;
            }
          t_y++;
          if(t_y == jh.high)
              {
                  t_y = 0;
                  t_x += slicesW[t_s++];
              }
      }
  offset[slices] = offset[slices-1];
  slice = 1; // next slice
  pixno = offset[0];
  pixelsInSlice = slicesW[0];
#endif

#ifdef LIBRAW_LIBRARY_BUILD
  LibRaw_byte_buffer *buf=NULL;
  if(data_size)
      buf = ifp->make_byte_buffer(data_size);
  LibRaw_bit_buffer bits;
#endif
  for (jrow=0; jrow < jh.high; jrow++) {
#ifdef LIBRAW_LIBRARY_BUILD
      if (buf)
          rp = ljpeg_row_new (jrow, &jh,bits,buf);
      else
#endif
    rp = ljpeg_row (jrow, &jh);

    if (load_flags & 1)
      row = jrow & 1 ? height-1-jrow/2 : jrow/2;
    for (jcol=0; jcol < jwide; jcol++) {
      val = *rp++;
      if (jh.bits <= 12)
	val = curve[val & 0xfff];
#ifndef LIBRAW_LIBRARY_BUILD
      // slow dcraw way to calculate row/col
      if (cr2_slice[0]) {
	jidx = jrow*jwide + jcol;
	i = jidx / (cr2_slice[1]*jh.high);
	if ((j = i >= cr2_slice[0]))
		 i  = cr2_slice[0];
	jidx -= i * (cr2_slice[1]*jh.high);
	row = jidx / cr2_slice[1+j];
	col = jidx % cr2_slice[1+j] + i*cr2_slice[1];
      }
#else
      // new fast one, but for data_size defined only (i.e. new CR2 format, not 1D/1Ds)
      if(buf)
          {
              if(!(load_flags & 1))
                  row = pixno/raw_width;
              col = pixno % raw_width;
              pixno++;
              if (0 == --pixelsInSlice)
                  {
                    if(slice > slices)
                      {
                        free(offset);
                        throw LIBRAW_EXCEPTION_IO_CORRUPT;
                      }
                      unsigned o = offset[slice++];
                      pixno = o & 0x0fffffff;
                      pixelsInSlice = slicesW[o>>28];
                  }
          }
#endif

      if(row>raw_height)
#ifdef LIBRAW_LIBRARY_BUILD
      {
        free(offset);
        throw LIBRAW_EXCEPTION_IO_CORRUPT;
      }
#else
        longjmp (failure, 3);
#endif
#ifndef LIBRAW_LIBRARY_BUILD

      if (raw_width == 3984 && (col -= 2) < 0)
              col += (row--,raw_width);

      if ((unsigned) (row-top_margin) < height) {
	c = FC(row-top_margin,col-left_margin);
	if ((unsigned) (col-left_margin) < width) {
	  BAYER(row-top_margin,col-left_margin) = val;
	  if (min > val) min = val;
	} else if (col > 1 && (unsigned) (col-left_margin+2) > width+3)
	  cblack[c] += (cblack[4+c]++,val);
      }
#else
      if (raw_width == 3984)
          {
              if ( (col -= 2) < 0)
                  col += (row--,raw_width);
              if(row >= 0 && row < raw_height && col >= 0 && col < raw_width)
                  RBAYER(row,col) = val;
          }
      else
          RBAYER(row,col) = val;

      if ((unsigned) (row-top_margin) < height)
          {
              // within image height
              if ((unsigned) (col-left_margin) < width)
                  {
                      // within image area, save min
                      if(save_min)
                          if (min > val) min = val;
                  }
              else if (col > 1 && (unsigned) (col-left_margin+2) > width+3)
                  {
                      c = FC(row-top_margin,col-left_margin);
                      cblack[c] += (cblack[4+c]++,val);
                  }
          }
#endif

#ifndef LIBRAW_LIBRARY_BUILD
      if (++col >= raw_width)
	col = (row++,0);
#else
      if(!buf) // 1D or 1Ds case
         if (++col >= raw_width)
            col = (row++,0);
#endif
    }
  }
  ljpeg_end (&jh);
  FORC4 if (cblack[4+c]) cblack[c] /= cblack[4+c];
  if (!strcasecmp(make,"KODAK"))
    black = min;
#ifdef LIBRAW_LIBRARY_BUILD
  if(buf)
      delete buf;
  free(offset);
#endif
}


void CLASS sony_load_raw()
{
  uchar head[40];
  ushort *pixel;
  unsigned i, key, row, col;

  fseek (ifp, 200896, SEEK_SET);
  fseek (ifp, (unsigned) fgetc(ifp)*4 - 1, SEEK_CUR);
  order = 0x4d4d;
  key = get4();
  fseek (ifp, 164600, SEEK_SET);
  fread (head, 1, 40, ifp);
  sony_decrypt ((unsigned int *) head, 10, 1, key);
  for (i=26; i-- > 22; )
    key = key << 8 | head[i];
  fseek (ifp, data_offset, SEEK_SET);
  pixel = (ushort *) calloc (raw_width, sizeof *pixel);
  merror (pixel, "sony_load_raw()");
  for (row=0; row < height; row++) {
    if (fread (pixel, 2, raw_width, ifp) < raw_width) derror();
    sony_decrypt ((unsigned int *) pixel, raw_width/2, !row, key);
    for (col=9; col < left_margin; col++)
      black += ntohs(pixel[col]);
#ifndef LIBRAW_LIBRARY_BUILD
    for (col=0; col < width; col++)
      if ((BAYER(row,col) = ntohs(pixel[col+left_margin])) >> 14)
	derror();
#else
    for (col=0; col < raw_width; col++)
        {
            RBAYER(row,col) = ntohs(pixel[col]);
            if(col >= left_margin && col < width+left_margin
               && (RBAYER(row,col)>>14))
                derror();
        }
#endif
   }
  free (pixel);
  if (left_margin > 9)
    black /= (left_margin-9) * height;
  maximum = 0x3ff0;
}


void CLASS adobe_dng_load_raw_nc()
{
  ushort *pixel, *rp;
  int row, col;

  pixel = (ushort *) calloc (raw_width * tiff_samples, sizeof *pixel);
  merror (pixel, "adobe_dng_load_raw_nc()");

#ifdef LIBRAW_LIBRARY_BUILD
  int dsz= raw_height*raw_width * tiff_samples * tiff_bps/8;
  LibRaw_byte_buffer *buf = NULL;
  if (tiff_bps != 16)
      {
          buf = ifp->make_byte_buffer(dsz);
      }
  LibRaw_bit_buffer bits;
#endif

  for (row=0; row < raw_height; row++) {
    if (tiff_bps == 16)
      read_shorts (pixel, raw_width * tiff_samples);
    else {
#ifdef LIBRAW_LIBRARY_BUILD
        bits.reset();
        for (col=0; col < raw_width * tiff_samples; col++)
            pixel[col] = bits._getbits(buf,tiff_bps,zero_after_ff);

#else
      getbits(-1);
      for (col=0; col < raw_width * tiff_samples; col++)
	pixel[col] = getbits(tiff_bps);
#endif
    }
    for (rp=pixel, col=0; col < raw_width; col++)
      adobe_copy_pixel (row, col, &rp);
  }
  free (pixel);
#ifdef LIBRAW_LIBRARY_BUILD
    if(buf)
        delete buf;
#endif
}


void CLASS redcine_load_raw()
{
#ifndef NO_JASPER
  int c, row, col;
  jas_stream_t *in;
  jas_image_t *jimg;
  jas_matrix_t *jmat;
  jas_seqent_t *data;
  ushort *img, *pix;

  jas_init();
#ifndef LIBRAW_LIBRARY_BUILD
  in = jas_stream_fopen (ifname, "rb");
#else
  in = (jas_stream_t*)ifp->make_jas_stream();
  if(!in)
          throw LIBRAW_EXCEPTION_DECODE_JPEG2000;
#endif
  jas_stream_seek (in, data_offset+20, SEEK_SET);
  jimg = jas_image_decode (in, -1, 0);
#ifndef LIBRAW_LIBRARY_BUILD
  if (!jimg) longjmp (failure, 3);
#else
  if(!jimg)
      {
          jas_stream_close (in);
          throw LIBRAW_EXCEPTION_DECODE_JPEG2000;
      }
#endif
  jmat = jas_matrix_create (height/2, width/2);
  merror (jmat, "redcine_load_raw()");
  img = (ushort *) calloc ((height+2)*(width+2), 2);
  merror (img, "redcine_load_raw()");
  FORC4 {
    jas_image_readcmpt (jimg, c, 0, 0, width/2, height/2, jmat);
    data = jas_matrix_getref (jmat, 0, 0);
    for (row = c >> 1; row < height; row+=2)
      for (col = c & 1; col < width; col+=2)
	img[(row+1)*(width+2)+col+1] = data[(row/2)*(width/2)+col/2];
  }
  for (col=1; col <= width; col++) {
    img[col] = img[2*(width+2)+col];
    img[(height+1)*(width+2)+col] = img[(height-1)*(width+2)+col];
  }
  for (row=0; row < height+2; row++) {
    img[row*(width+2)] = img[row*(width+2)+2];
    img[(row+1)*(width+2)-1] = img[(row+1)*(width+2)-3];
  }
  for (row=1; row <= height; row++) {
    pix = img + row*(width+2) + (col = 1 + (FC(row,1) & 1));
    for (   ; col <= width; col+=2, pix+=2) {
      c = (((pix[0] - 0x800) << 3) +
	pix[-(width+2)] + pix[width+2] + pix[-1] + pix[1]) >> 2;
      pix[0] = LIM(c,0,4095);
    }
  }
  for (row=0; row < height; row++)
    for (col=0; col < width; col++)
      RBAYER(row,col) = curve[img[(row+1)*(width+2)+col+1]];
  free (img);
  jas_matrix_destroy (jmat);
  jas_image_destroy (jimg);
  jas_stream_close (in);
#endif
}


void CLASS parse_ciff (int offset, int length)
{
  int tboff, nrecs, c, type, len, save, wbi=-1;
  ushort key[] = { 0x410, 0x45f3 };

  fseek (ifp, offset+length-4, SEEK_SET);
  tboff = get4() + offset;
  fseek (ifp, tboff, SEEK_SET);
  nrecs = get2();
  if (nrecs > 100) return;
  while (nrecs--) {
    type = get2();
    len  = get4();
    save = ftell(ifp) + 4;
    fseek (ifp, offset+get4(), SEEK_SET);
    if ((((type >> 8) + 8) | 8) == 0x38)
      parse_ciff (ftell(ifp), len);	/* Parse a sub-table */

    if (type == 0x0810)
      fread (artist, 64, 1, ifp);
    if (type == 0x080a) {
      fread (make, 64, 1, ifp);
      fseek (ifp, ((INT64)strlen(make)) - 63, SEEK_CUR);
      fread (model, 64, 1, ifp);
    }
    if (type == 0x1810) {
      fseek (ifp, 12, SEEK_CUR);
      flip = get4();
    }
    if (type == 0x1835)			/* Get the decoder table */
      tiff_compress = get4();
    if (type == 0x2007) {
      thumb_offset = ftell(ifp);
      thumb_length = len;
    }
    if (type == 0x1818) {
      shutter = pow (2.0f, -int_to_float((get4(),get4())));
      aperture = pow (2.0f, int_to_float(get4())/2);
    }
    if (type == 0x102a) {
      iso_speed = pow (2.0, (get4(),get2())/32.0 - 4) * 50;
      aperture  = pow (2.0, (get2(),(short)get2())/64.0);
      shutter   = pow (2.0,-((short)get2())/32.0);
      wbi = (get2(),get2());
      if (wbi > 17) wbi = 0;
      fseek (ifp, 32, SEEK_CUR);
      if (shutter > 1e6) shutter = get2()/10.0;
    }
    if (type == 0x102c) {
      if (get2() > 512) {		/* Pro90, G1 */
	fseek (ifp, 118, SEEK_CUR);
	FORC4 cam_mul[c ^ 2] = get2();
#ifdef LIBRAW_LIBRARY_BUILD
        color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED;
#endif
      } else {				/* G2, S30, S40 */
	fseek (ifp, 98, SEEK_CUR);
	FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get2();
#ifdef LIBRAW_LIBRARY_BUILD
        color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED;
#endif
      }
    }
    if (type == 0x0032) {
      if (len == 768) {			/* EOS D30 */
	fseek (ifp, 72, SEEK_CUR);
	FORC4 cam_mul[c ^ (c >> 1)] = 1024.0 / get2();
#ifdef LIBRAW_LIBRARY_BUILD
        color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED;
#endif
	if (!wbi) cam_mul[0] = -1;	/* use my auto white balance */
      } else if (!cam_mul[0]) {
	if (get2() == key[0])		/* Pro1, G6, S60, S70 */
	  c = (strstr(model,"Pro1") ?
	      "012346000000000000":"01345:000000006008")[wbi]-'0'+ 2;
	else {				/* G3, G5, S45, S50 */
	  c = "023457000000006000"[wbi]-'0';
	  key[0] = key[1] = 0;
	}
	fseek (ifp, 78 + c*8, SEEK_CUR);
	FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get2() ^ key[c & 1];
#ifdef LIBRAW_LIBRARY_BUILD
        color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED;
#endif
	if (!wbi) cam_mul[0] = -1;
      }
    }
    if (type == 0x10a9) {		/* D60, 10D, 300D, and clones */
      if (len > 66) wbi = "0134567028"[wbi]-'0';
      fseek (ifp, 2 + wbi*8, SEEK_CUR);
      FORC4 cam_mul[c ^ (c >> 1)] = get2();
#ifdef LIBRAW_LIBRARY_BUILD
      color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED;
#endif
    }
    if (type == 0x1030 && (0x18040 >> wbi & 1))
      ciff_block_1030();		/* all that don't have 0x10a9 */
    if (type == 0x1031) {
      raw_width = (get2(),get2());
      raw_height = get2();
    }
    if (type == 0x5029) {
      focal_len = len >> 16;
      if ((len & 0xffff) == 2) focal_len /= 32;
    }
    if (type == 0x5813) flash_used = int_to_float(len);
    if (type == 0x5814) canon_ev   = int_to_float(len);
    if (type == 0x5817) shot_order = len;
    if (type == 0x5834) unique_id  = len;
    if (type == 0x580e) timestamp  = len;
    if (type == 0x180e) timestamp  = get4();
#ifdef LOCALTIME
    if ((type | 0x4000) == 0x580e)
      timestamp = mktime (gmtime (&timestamp));
#endif
    fseek (ifp, save, SEEK_SET);
  }
}


  if (!hOutProfile)
      {
#ifdef LIBRAW_LIBRARY_BUILD
          imgdata.process_warnings |= LIBRAW_WARN_BAD_OUTPUT_PROFILE;
#endif
          goto quit;
      }


free_akl (struct akl *akl)
{
  if (akl->spec)
    free_keyserver_spec (akl->spec);

  xfree (akl);
}


release_keyid_list (keyid_list_t k)
{
  while (k)
    {
      keyid_list_t k2 = k->next;
      xfree (k);
      k = k2;
    }
}


get_primary_uid (KBNODE keyblock, size_t * uidlen)
{
  KBNODE k;
  const char *s;

  for (k = keyblock; k; k = k->next)
    {
      if (k->pkt->pkttype == PKT_USER_ID
	  && !k->pkt->pkt.user_id->attrib_data
	  && k->pkt->pkt.user_id->is_primary)
	{
	  *uidlen = k->pkt->pkt.user_id->len;
	  return k->pkt->pkt.user_id->name;
	}
    }
  s = user_id_not_found_utf8 ();
  *uidlen = strlen (s);
  return s;
}


SYSCALL_DEFINE(fallocate)(int fd, int mode, loff_t offset, loff_t len)
{
	struct file *file;
	int error = -EBADF;

	file = fget(fd);
	if (file) {
		error = do_fallocate(file, mode, offset, len);
		fput(file);
	}

	return error;
}


SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
	long ret;

	if (force_o_largefile())
		flags |= O_LARGEFILE;

	ret = do_sys_open(AT_FDCWD, filename, flags, mode);
	/* avoid REGPARM breakage on x86: */
	asmlinkage_protect(3, ret, filename, flags, mode);
	return ret;
}


SYSCALL_DEFINE1(close, unsigned int, fd)
{
	struct file * filp;
	struct files_struct *files = current->files;
	struct fdtable *fdt;
	int retval;

	spin_lock(&files->file_lock);
	fdt = files_fdtable(files);
	if (fd >= fdt->max_fds)
		goto out_unlock;
	filp = fdt->fd[fd];
	if (!filp)
		goto out_unlock;
	rcu_assign_pointer(fdt->fd[fd], NULL);
	FD_CLR(fd, fdt->close_on_exec);
	__put_unused_fd(files, fd);
	spin_unlock(&files->file_lock);
	retval = filp_close(filp, files);

	/* can't restart close syscall because file table entry was cleared */
	if (unlikely(retval == -ERESTARTSYS ||
		     retval == -ERESTARTNOINTR ||
		     retval == -ERESTARTNOHAND ||
		     retval == -ERESTART_RESTARTBLOCK))
		retval = -EINTR;

	return retval;

out_unlock:
	spin_unlock(&files->file_lock);
	return -EBADF;
}


struct file *filp_open(const char *filename, int flags, int mode)
{
	struct open_flags op;
	int lookup = build_open_flags(flags, mode, &op);
	return do_filp_open(AT_FDCWD, filename, &op, lookup);
}


int ssl_handshake_server_step( ssl_context *ssl )
{
    int ret = 0;

    if( ssl->state == SSL_HANDSHAKE_OVER )
        return( POLARSSL_ERR_SSL_BAD_INPUT_DATA );

    SSL_DEBUG_MSG( 2, ( "server state: %d", ssl->state ) );

    if( ( ret = ssl_flush_output( ssl ) ) != 0 )
        return( ret );

    switch( ssl->state )
    {
        case SSL_HELLO_REQUEST:
            ssl->state = SSL_CLIENT_HELLO;
            break;

        /*
         *  <==   ClientHello
         */
        case SSL_CLIENT_HELLO:
            ret = ssl_parse_client_hello( ssl );
            break;

        /*
         *  ==>   ServerHello
         *        Certificate
         *      ( ServerKeyExchange  )
         *      ( CertificateRequest )
         *        ServerHelloDone
         */
        case SSL_SERVER_HELLO:
            ret = ssl_write_server_hello( ssl );
            break;

        case SSL_SERVER_CERTIFICATE:
            ret = ssl_write_certificate( ssl );
            break;

        case SSL_SERVER_KEY_EXCHANGE:
            ret = ssl_write_server_key_exchange( ssl );
            break;

        case SSL_CERTIFICATE_REQUEST:
            ret = ssl_write_certificate_request( ssl );
            break;

        case SSL_SERVER_HELLO_DONE:
            ret = ssl_write_server_hello_done( ssl );
            break;

        /*
         *  <== ( Certificate/Alert  )
         *        ClientKeyExchange
         *      ( CertificateVerify  )
         *        ChangeCipherSpec
         *        Finished
         */
        case SSL_CLIENT_CERTIFICATE:
            ret = ssl_parse_certificate( ssl );
            break;

        case SSL_CLIENT_KEY_EXCHANGE:
            ret = ssl_parse_client_key_exchange( ssl );
            break;

        case SSL_CERTIFICATE_VERIFY:
            ret = ssl_parse_certificate_verify( ssl );
            break;

        case SSL_CLIENT_CHANGE_CIPHER_SPEC:
            ret = ssl_parse_change_cipher_spec( ssl );
            break;

        case SSL_CLIENT_FINISHED:
            ret = ssl_parse_finished( ssl );
            break;

        /*
         *  ==>   ChangeCipherSpec
         *        Finished
         */
        case SSL_SERVER_CHANGE_CIPHER_SPEC:
            ret = ssl_write_change_cipher_spec( ssl );
            break;

        case SSL_SERVER_FINISHED:
            ret = ssl_write_finished( ssl );
            break;

        case SSL_FLUSH_BUFFERS:
            SSL_DEBUG_MSG( 2, ( "handshake: done" ) );
            ssl->state = SSL_HANDSHAKE_WRAPUP;
            break;

        case SSL_HANDSHAKE_WRAPUP:
            ssl_handshake_wrapup( ssl );
            break;

        default:
            SSL_DEBUG_MSG( 1, ( "invalid state %d", ssl->state ) );
            return( POLARSSL_ERR_SSL_BAD_INPUT_DATA );
    }

    return( ret );
}


static int x509_get_crt_ext( unsigned char **p,
                             const unsigned char *end,
                             x509_cert *crt )
{
    int ret;
    size_t len;
    unsigned char *end_ext_data, *end_ext_octet;

    if( ( ret = x509_get_ext( p, end, &crt->v3_ext, 3 ) ) != 0 )
    {
        if( ret == POLARSSL_ERR_ASN1_UNEXPECTED_TAG )
            return( 0 );

        return( ret );
    }

    while( *p < end )
    {
        /*
         * Extension  ::=  SEQUENCE  {
         *      extnID      OBJECT IDENTIFIER,
         *      critical    BOOLEAN DEFAULT FALSE,
         *      extnValue   OCTET STRING  }
         */
        x509_buf extn_oid = {0, 0, NULL};
        int is_critical = 0; /* DEFAULT FALSE */

        if( ( ret = asn1_get_tag( p, end, &len,
                ASN1_CONSTRUCTED | ASN1_SEQUENCE ) ) != 0 )
            return( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS + ret );

        end_ext_data = *p + len;

        /* Get extension ID */
        extn_oid.tag = **p;

        if( ( ret = asn1_get_tag( p, end, &extn_oid.len, ASN1_OID ) ) != 0 )
            return( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS + ret );

        extn_oid.p = *p;
        *p += extn_oid.len;

        if( ( end - *p ) < 1 )
            return( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS +
                    POLARSSL_ERR_ASN1_OUT_OF_DATA );

        /* Get optional critical */
        if( ( ret = asn1_get_bool( p, end_ext_data, &is_critical ) ) != 0 &&
            ( ret != POLARSSL_ERR_ASN1_UNEXPECTED_TAG ) )
            return( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS + ret );

        /* Data should be octet string type */
        if( ( ret = asn1_get_tag( p, end_ext_data, &len,
                ASN1_OCTET_STRING ) ) != 0 )
            return( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS + ret );

        end_ext_octet = *p + len;

        if( end_ext_octet != end_ext_data )
            return( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS +
                    POLARSSL_ERR_ASN1_LENGTH_MISMATCH );

        /*
         * Detect supported extensions
         */
        if( ( OID_SIZE( OID_BASIC_CONSTRAINTS ) == extn_oid.len ) &&
                memcmp( extn_oid.p, OID_BASIC_CONSTRAINTS, extn_oid.len ) == 0 )
        {
            /* Parse basic constraints */
            if( ( ret = x509_get_basic_constraints( p, end_ext_octet,
                    &crt->ca_istrue, &crt->max_pathlen ) ) != 0 )
                return ( ret );
            crt->ext_types |= EXT_BASIC_CONSTRAINTS;
        }
        else if( ( OID_SIZE( OID_NS_CERT_TYPE ) == extn_oid.len ) &&
                memcmp( extn_oid.p, OID_NS_CERT_TYPE, extn_oid.len ) == 0 )
        {
            /* Parse netscape certificate type */
            if( ( ret = x509_get_ns_cert_type( p, end_ext_octet,
                    &crt->ns_cert_type ) ) != 0 )
                return ( ret );
            crt->ext_types |= EXT_NS_CERT_TYPE;
        }
        else if( ( OID_SIZE( OID_KEY_USAGE ) == extn_oid.len ) &&
                memcmp( extn_oid.p, OID_KEY_USAGE, extn_oid.len ) == 0 )
        {
            /* Parse key usage */
            if( ( ret = x509_get_key_usage( p, end_ext_octet,
                    &crt->key_usage ) ) != 0 )
                return ( ret );
            crt->ext_types |= EXT_KEY_USAGE;
        }
        else if( ( OID_SIZE( OID_EXTENDED_KEY_USAGE ) == extn_oid.len ) &&
                memcmp( extn_oid.p, OID_EXTENDED_KEY_USAGE, extn_oid.len ) == 0 )
        {
            /* Parse extended key usage */
            if( ( ret = x509_get_ext_key_usage( p, end_ext_octet,
                    &crt->ext_key_usage ) ) != 0 )
                return ( ret );
            crt->ext_types |= EXT_EXTENDED_KEY_USAGE;
        }
        else if( ( OID_SIZE( OID_SUBJECT_ALT_NAME ) == extn_oid.len ) &&
                memcmp( extn_oid.p, OID_SUBJECT_ALT_NAME, extn_oid.len ) == 0 )
        {
            /* Parse extended key usage */
            if( ( ret = x509_get_subject_alt_name( p, end_ext_octet,
                    &crt->subject_alt_names ) ) != 0 )
                return ( ret );
            crt->ext_types |= EXT_SUBJECT_ALT_NAME;
        }
        else
        {
            /* No parser found, skip extension */
            *p = end_ext_octet;

#if !defined(POLARSSL_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION)
            if( is_critical )
            {
                /* Data is marked as critical: fail */
                return ( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS +
                        POLARSSL_ERR_ASN1_UNEXPECTED_TAG );
            }
#endif
        }
    }

    if( *p != end )
        return( POLARSSL_ERR_X509_CERT_INVALID_EXTENSIONS +
                POLARSSL_ERR_ASN1_LENGTH_MISMATCH );

    return( 0 );
}