Jump to content

mvanrijnen

Members
  • Content Count

    477
  • Joined

  • Last visited

  • Days Won

    1

Posts posted by mvanrijnen


  1. Also the method of inline sending the attachments is limited to a size ( i believe 3MB),  my code (i'm in a hurry, so just a dump), you can get the idea how to from this.

     


    
    const
    
      CNST_RESOURCE_CREATEUPLOADSESSION = 'users/{sendasemailaddress}/messages/{messageid}/attachments/createUploadSession';
      CNST_RESOURCE_CREATEDRAFTMESSAGE = 'users/{sendasemailaddress}/mailFolders/drafts/messages';
      CNST_RESOURCE_SENDCREATEDRAFT = 'users/{sendasemailaddress}/messages/{messageid}/send';
     
    
    

    function THSMSGraphAPI.FetchUploadURL(const AUploadSize: integer; const AFileName, AMessageID, AContentID, AContentType  : string; const AIsInline : Boolean): string;
    var
      fuploadRequest : TRESTRequest;
      fuploadResponse : TMSGraphUploadSessionResponse;
      attreq : TMSGraphUploadSessionRequest;
    begin
      Result := '';
      fuploadRequest := TRESTRequest.Create(nil);
      try
        fuploadRequest.Client := FRestClient;
        fuploadRequest.Method := TRESTRequestMethod.rmPOST;
        fuploadRequest.Resource := CNST_RESOURCE_CREATEUPLOADSESSION.Replace('{sendasemailaddress}', AccountEmailAddress, [rfReplaceAll, rfIgnoreCase]).Replace('{messageid}', AMessageID, [rfReplaceAll, rfIgnoreCase]);
        attreq.AttachmentItem.attachmentType := 'file';
        attreq.AttachmentItem.name := ExtractFileName(AFileName);
        attreq.AttachmentItem.size := AUploadSize;
    
        attreq.AttachmentItem.isInline := (AIsInline) or (not AContentID.IsEmpty);
        attreq.AttachmentItem.contentID := AContentID;
        attreq.AttachmentItem.contentType := AContentType;
    
        fuploadRequest.AddBody(attreq.AsJSON, TRESTContentType.ctAPPLICATION_JSON);
        fuploadRequest.Execute();
        if not LogInvalidResponse(fuploadRequest.Response, True, 'THSMSGraphAPI.FetchUploadURL') then
        begin
          fuploadResponse := TMSGraphUploadSessionResponse.FromJSON(fuploadRequest.Response.Content);
          Result := fuploadResponse.uploadUrl;
        end;
      finally
        fuploadRequest.Free;
      end;
    end;
    
    procedure THSMSGraphAPI.AddLargeAttachments(const AEmailMessageAttachments : TMvREMailAttachments; const AMessageID : string);
              function CreateSourceStream(const AAttachment : TMvREMailAttachment) : TStream;
              begin
                Result := nil;
                if AAttachment.IsFile then
                   Result := TFileStream.Create(AAttachment.FileName, fmOpenRead or fmShareDenyWrite)
                else
                   Result := TStringStream.Create(AAttachment.PayLoad);
              end;
    
              procedure UploadParts(const AUploadUrl : string; const ASourceStream : TStream; const AContentType : string);
              var
                memStream : TMemoryStream;
                fuploadPartRequest : TRESTRequest;
                hdr : string;
                size,
                done,
                todo  : integer;
              begin
                fuploadPartRequest := TRESTRequest.Create(nil);
                memStream := TMemoryStream.Create;
                try
                  fuploadPartRequest.URLAlreadyEncoded := True;
                  fuploadPartRequest.Client := FUplClient;
                  fuploadPartRequest.Method := TRESTRequestMethod.rmPUT;
    
                  FUplClient.BaseURL := AUploadUrl;
                  size := ASourceStream.Size;
                  done := 0;
                  todo := 0;
                  ASourceStream.Position := 0;
                  while (done<size) do
                  begin
                    todo := Min(CNST_MAXATTSIZE, size-done);
                    memStream.Clear;
                    ASourceStream.Position := done;
                    memStream.CopyFrom(ASourceStream, todo);
                    memStream.Position := 0;
                    hdr := 'bytes ' + (done).ToString + '-' + (done+todo-1).ToString + '/' + size.ToString;
                    fuploadPartRequest.ClearBody;
                    fuploadPartRequest.Params.Clear;
                    fuploadPartRequest.AddParameter('Content-Length', todo.ToString, TRESTRequestParameterKind.pkHTTPHEADER);
                    fuploadPartRequest.AddParameter('Content-Range',  hdr, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
                    if not AContentType.IsEmpty then
                      fuploadPartRequest.AddParameter('Content-Type',   AContentType, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
                    fuploadPartRequest.AddBody(memStream, TRESTContentType.ctAPPLICATION_OCTET_STREAM);
                    ExecuteRequest(fuploadPartRequest, 'THSMSGraphAPI.AddLargeAttachments.UploadParts', True);
                    LogInvalidResponse(fuploadPartRequest.Response, True, 'UploadParts');
                    done := done + todo;
                  end;
                finally
                  memStream.Free;
                  fuploadPartRequest.Free;
                end;
              end;
    var
      att : TMvREMailAttachment;
      url : string;
      stream : TStream;
    begin
      for att in AEmailMessageAttachments do
          //if att.IsLargeAttachment then
          begin
            stream := CreateSourceStream(att);
            try
    //          url := FetchUploadURL(stream.Size, att.FileName, AMessageID);
              url := FetchUploadURL(stream.Size, att.FileName, AMessageID, att.ContentID, att.ContentType, att.IsInline);
              UploadParts(url, stream, att.ContentType);
            finally
              stream.Free;
            end;
          end;
    end;
     
    
     
    
    function THSMSGraphAPI.SendEmailWithLargeAttachments(const AEmailMessage: TMvREMailMessage; AAPIMessage: TMSGraphSendEmailMessageBody): Boolean;
              function FetchImmutableID(const AID : string) : string;
              var
                req : TRESTRequest;
                request : TMSGraphTranslateExchangeIdsRequest;
                responseJSON,
                requestJSON : string;
                response : TMSGraphTranslateExchangeIdsResponse;
              begin
                Result := '';
                req := TRESTRequest.Create(nil);
                try
                  try
                    req.Client := FRestClient;
                    req.Method := TRESTRequestMethod.rmPOST;
                    req.Resource := 'users/{sendasemailaddress}/translateExchangeIds'.Replace('{sendasemailaddress}', AccountEmailAddress, [rfReplaceAll, rfIgnoreCase]);
    
                    SetLength(request.inputIds, 1);
                    request.inputIds[0] := AID;
                    request.targetIdType := 'restImmutableEntryId';
                    request.sourceIdType := 'restId';
                    TgoBsonSerializer.Serialize( request, requestJSON);
                    req.AddBody(requestJSON, TRESTContentType.ctAPPLICATION_JSON);
                    req.Execute();
    
                    responseJSON := req.Response.Content;
                    TgoBsonSerializer.Deserialize(responseJSON, response);
                    if length(response.value)>0 then
                       Result := response.value[0].targetId;
                  except
                    on e: exception do
                    begin
                      Result := '';
                      raise Exception.CreateFmt('[THSMSGraphAPI.SendEmailWithLargeAttachments.FetchImmutableId]: %s\n%s', [e.ClassName, e.Message]);
                    end;
                  end;
                finally
                  req.Free;
                end;
              end;
    
              function FetchMessagesForInternetMessageID(const AInternetMessageID : string) : TArray<string>;
              var
                req : TRESTRequest;
                request : TMSGraphTranslateExchangeIdsRequest;
                responseJSON,
                requestJSON : string;
                response : TMSGraphTranslateExchangeIdsResponse;
              begin
                SetLength(Result, 0);
                req := TRESTRequest.Create(nil);
                try
                  try
                    req.Client := FRestClient;
                    req.Method := TRESTRequestMethod.rmGET;
                    req.Resource := 'users/{sendasemailaddress}/messages?$filter=internetMessageId eq ''{internetmessageid}'''
                                    .Replace('{sendasemailaddress}', AccountEmailAddress, [rfReplaceAll, rfIgnoreCase])
                                    .Replace('{internetmessageid}', TidUri.ParamsEncode(AInternetMessageID), [rfReplaceAll, rfIgnoreCase]);
                    req.Execute();
                  except
                    on e: exception do
                    begin
                      raise Exception.CreateFmt('[THSMSGraphAPI.SendEmailWithLargeAttachments.FetchMessagesForInternetMessageID]: %s\n%s', [e.ClassName, e.Message]);
                    end;
                  end;
                finally
                  req.Free;
                end;
              end;
    var
      req1,
      req2 : TRESTRequest;
      draft : TMSGraphDraftResponse;
      draftremoved,
      step1done : boolean;
      removemsgid,
      immid : string;
    begin
      Result := False;
      immid := '';
      removemsgid := '';
      step1done := False;
      draftremoved := False;
      req1 := TRESTRequest.Create(nil);
      req2 := TRESTRequest.Create(nil);
      try
        try
          req1.Client := FRestClient;
          req1.Method := TRESTRequestMethod.rmPOST;
          req2.Client := FRestClient;
          req2.Method := TRESTRequestMethod.rmPOST;
    
          req1.Resource := CNST_RESOURCE_CREATEDRAFTMESSAGE.Replace('{sendasemailaddress}', AccountEmailAddress, [rfReplaceAll, rfIgnoreCase]);
          req1.AddBody(AAPIMessage.message.AsJson(), TRESTContentType.ctAPPLICATION_JSON);
          try
            req1.Execute();
          except
            on e: exception do
            begin
              raise Exception.CreateFmt('[THSMSGraphAPI.SendEmailWithLargeAttachments.Req1]: %s\n%s', [e.ClassName, e.Message]);
            end;
          end;
    
          if not LogInvalidResponse(req1.Response, True, 'THSMSGraphAPI.SendEmailWithLargeAttachments.1') then
          begin
            draft := TMSGraphDraftResponse.FromJSON(req1.Response.Content);
            removemsgid := draft.id;
            step1done := IsValidResponse(req1.Response);
            if not (AEmailMessage.SaveOnSend) then
            begin
               immid := FetchImmutableID(draft.id);
               removemsgid := immid;
            end;
            {$ifdef debug}
            if not immid.IsEmpty then
               LogExInfo('ImmutableID: %s', [immid], 'THSMSGraphAPI.SendEmailWithLargeAttachments');
            {$endif}
            //raise Exception.Create('TEST Error Message');
            try
              AddLargeAttachments(AEmailMessage.Attachments, draft.id);
            except
              on e: exception do
              begin
                raise Exception.CreateFmt('[THSMSGraphAPI.SendEmailWithLargeAttachments.AddLargeAttachments]: %s\n%s', [e.ClassName, e.Message]);
              end;
            end;
          end;
    
          req2.Resource := CNST_RESOURCE_SENDCREATEDRAFT.Replace('{sendasemailaddress}', AccountEmailAddress, [rfReplaceAll, rfIgnoreCase]).Replace('{messageid}', draft.id, [rfReplaceAll, rfIgnoreCase]);
          try
            req2.Execute();
          except
            on e: exception do
            begin
              raise Exception.CreateFmt('[THSMSGraphAPI.SendEmailWithLargeAttachments.Req2]: %s\n%s', [e.ClassName, e.Message]);
            end;
          end;
    
          if not LogInvalidResponse(req2.Response, True, 'SendWithLargeAttachments.2') then
          begin
            Result := True;
            if not (AEmailMessage.SaveOnSend) then
            begin
               if not immid.IsEmpty then
                  draftremoved := TryRemoveEmailMessage(immid)
               else
                 LogExWarn('Could not remove draft sendmessage because empty immutableid', 'THSMSGraphAPI.SendEmailWithLargeAttachments');
            end;
          end;
        except
          on e: exception do
          begin
            Result := False;
            if (not draftremoved) and (step1done) and (not removemsgid.IsEmpty) then
            begin
               Sleep(1000);
               draftremoved := TryRemoveEmailMessage(removemsgid);
               if draftremoved then
                  LogExWarn('Draft message with id: %s removed.', [removemsgid], 'THSMSGraphAPI.SendEmailWithLargeAttachments')
               else
                  LogExError('Draft message with id: %s was not removed!', [removemsgid], 'THSMSGraphAPI.SendEmailWithLargeAttachments')
            end;
            raise;
          end;
        end;
      finally
        req2.Free;
        req1.Free;
      end;
    end;
    

  2. i've build a service (called ProtoBridge, not public available), which does just this.

    But more 😉

     

    It can act as a simple bridge between older machines/services to forward emails to o365/gmail  (oauth2) etc etc. 

    It can also serve as a ftp server, and  you can configure different virtual ftp folders which direct the incoming file(s) to a specific email adress

     

    so you can "bridge" protocols

    it can act as server for:

    * simple smtp

    * ftp

    * local folders

    * smb folders

     

    and forward to:

    * simple smtp

    * smtp (oauth2 etc)

    * O365 (MS Graph API)

    * Exchange EWS

    * ftp

    * local folder

    * smb folder

     

    It's using INDY for all the protocols (except MS Graph API & EWS), off course is this a service which is not intended to use on a public server.

    (there is a white & blacklist on IP numbers available)

     

     

     

     

     


  3. 9 minutes ago, JonRobertson said:

    Are your computers connected to the Internet? Any application can be hijacked by an intrusion from the outside, even applications developed internally.

    Yes. my question  was more, (a discussion  i had on this forum a few years ago also), do we benefit  for preventing false positives using signing (makes it easier turning the mgmt in positive direction, so they don't only see it as a cost) ?

    (we are going to implement signing anyway).

     


  4. 16 hours ago, JonRobertson said:

    If your customers use an Endpoint Protection and Response product, code signing is critical. The one we use sometimes complains even when the executable is signed with a valid certificate. It is a pain in the rear. But it is essential due to the number and sophistication of cyber threats today. Two-factor or multi-factor authentication is also a pain that I have to put up with daily. I can't do my job without my phone. :classic_angry:

     

    The Internet is a tremendous resource. But there are days that I miss the simplicity of 8-bit computing.

     

    So we create only software for internal use, using (at the moment) , do we benefit from code signing ? 

     


  5. Not a direct answer, but we (in our custom ERP solution), put the emails in a table (kind of a mailqueue) , and another process (service) polls and/or gets signaled when to scan the table for messages to send. 

    With this we have better control, and are sure that the email is send in the background.


    We also can control a minimum, maximum datetime to send the message etc etc.

    (sometimes an email message is irrelevant if it didn't get send after a specific date)
     
    So not high prio mails are send like every 5 minutes, high prio directly.


    Only problem is that interactive emails (send with a client like outlook) are not under the same control. 

     

    The service itself is highly configurable, with different outgoing servers, accounts etc etc., 

     


  6. At what point you are stuck?

     

     

    You can use the "TRestClient.OnNeedClientCertificate" event, simple example code:

    We check the certificate name, which has to begin with a certain value (stored in the CNST_CERT_PREFIX constant), and of course it has to be a valid certificate.
     

    (i believe you need the client certificate installed in "user" context, not sure about that, long time ago i was busy with this).

     

     

    procedure TMyProgram.DoOnClientCertificateNeeded(const Sender: TObject; const ARequest: TURLRequest; const ACertificateList: TCertificateList; var AnIndex: Integer);
    var
      idx : integer;
    begin
      if CNST_CERT_PREFIX.IsEmpty then
         raise Exception.Create('[TMyProgram.DoOnClientCertificateNeeded] CNST_CERT_PREFIX is empty.');
          for idx := 0 to ACertificateList.Count - 1 do 
      begin
        if ACertificateList[idx].CertName.StartsWith(CNST_CERT_PREFIX) then
        begin
          if (ACertificateList[idx].Start<=Now) and (ACertificateList[idx].Expiry>Now) then
          begin
             AnIndex := idx;
             break;
          end
          else
            raise Exception.Create('[TMyProgram.DoOnClientCertificateNeeded] Client Certificate Expired.');
        end;
      end;
    end;
       
×