FB_init

Showing posts with label software. Show all posts
Showing posts with label software. Show all posts

Wednesday, July 14, 2021

Regular expression plug-in for Sublime Text

 I often need to look at logs. I needed a way to quickly highlight different parts of the logs. I created a plugin for Sublime Text to do that. 

  Here are instructions to create a plugins. 

  Here is my plugin:


  You can invoke it with

view.run_command("reggae", {"pattern_args": ["pattern 1", "pattern 2"]}) 

Tuesday, February 28, 2017

Analyzing request rates using R

Problem statement
A server exposes an API on the Internet, serving requests. A request has a certain IP as origin. The question is: how should one configure limits on the web server to avoid abusive requests? 'Abusive requests' are a series of requests that happen in bursts of a 'high rate' per second. Nginx allows one to limit request rates. See http://nginx.org/en/docs/http/ngx_http_limit_req_module.html . Above the threshold, Nginx responds with HTTP error 503.

General approach
The general approach is to load web server log data and to measure the request rates from it. After tabulating the data, we can decide what rates are 'abusive'. We'll configure Nginx with 
 limit_req_zone $binary_remote_addr  
meaning that it will apply the request rate limit to the source IP.

Tools
We'll use some Unix commands to pre-process the log files. If you are using Windows, consider installing Cygwin. See https://cygwin.com/install.html .
For R, we can use either a Jupyter notebook or R Studio. Jupyter was useful to organize the commands in sequence, while R Studio was useful to try ad-hoc commands on the data. Both Jupyter notebooks and R Studio are included in Anaconda Navigator. Begin with the "Miniconda" install. See https://conda.io/miniconda.html .


Install R Studio and Jupyter notebook from Anaconda Navigator.

Pre-processing the web server logs
The log files have lines including many information. We just need the date+time and source IP.

We cut twice, first on double-quotes, then on whitespace. Then we remove some extra characters if needed.


 cut -d "\"" -f1,8 fdpapi_ssl_access_w2_21.log | cut -d " " -f4,6 | sed 's/\[//' | sed 's/,//' | sed 's/\"//' > fdpapi_ssl_access_w2_21.csv  


The end result are lines with just the date+time and source IP.



Analysing the logs

The first lines of the R script load the csv file. Then, sort the rows by IP.


 library(readr)  
 logrows <- read_delim("~/TGIF_work/nginx_thr_FDPCARE-325/fdpapi_ssl_access_w_tutti.csv",   
   " ", escape_double = FALSE, col_names = FALSE,   
   col_types = cols(`coldatetime` = col_datetime(format = "%d/%b/%Y:%H:%M:%S"),   
     X1 = col_datetime(format = "%d/%b/%Y:%H:%M:%S")),   
   trim_ws = TRUE)  
 logrows <- logrows[order(logrows$X2),]  

You can peek into the log rows.
head(logrows)

X1X2
2017-02-19 00:00:04100.0.XX.244
2017-02-19 00:00:04100.0.XX.244
2017-02-19 00:03:51100.1.XXX.223
2017-02-19 00:03:51100.1.XXX.223
2017-02-19 00:03:52100.1.XXX.223
2017-02-19 00:02:48100.1.XXX.60


R has a special type called 'factor' to represent unique 'categories' in the data. Each element in the factor is called a 'level'. In our case, unique IPs are levels.


 tb <- table(logrows$X2)   

 ipsfactor <- factor(logrows$X2, levels = names(tb[order(tb, decreasing = TRUE)]))  



Let us define a function to translate the request times into numbers (minutes) relative to the first request. Then, make a histogram of the requests, placing them into 'bins' of one minute.


 # x is a vector with the datetimes for a certain IP  

 findfreq <- function(x) {  
   thesequence <- as.numeric(x-min(x), units="mins")  
   hist(thesequence, breaks = seq(from = 0, to = ceiling(max(thesequence))+1, by=1), plot=FALSE)$counts  
 }  



Here we could count the frequency of IPs using the factor and the function. That is, we could apply the function for each IP. This would allow for the visualization of an 'intermediary' step of the process.


 # Intermediary steps  

 # ipscount <- tapply(logrows$X1, ipsfactor, findfreq, simplify=FALSE)  
 # ttipscount <- t(t(ipscount))  




Transposing the array twice simplifies the visualization.
Let's now define another similar function that only keeps the maximum.


 findmaxfreq <- function(x) {  
   max(findfreq(x))  
 }  

Aggregate the data using the new function.


 ipsagg <- aggregate( logrows$X1 ~ ipsfactor, FUN = findmaxfreq )  

Display the top IPs sorted by requests per minute descending. These are the 'top abusers'. The table shows that IP 51.XXX.56.29 sent 1160 requests per minute at one point.


 head(ipsagg[order(ipsagg$`logrows$X1`, decreasing = TRUE),])  


ipsfactorlogrows$X1
51.XXX.56.291160
64.XX.231.130711
64.XX.231.169705
68.XX.69.191500
217.XXX.203.205462
64.XX.231.157458

Display quantiles from 80% to 100%


 quantile(ipsagg$`logrows$X1`, seq(from=0.99, to=1, by=0.001))  



This table is saying that 99.6% of the IPs had the max request rate of 63 requests per minute or less. Plot the quantiles.


 ipsaggrpm <- ipsagg$`logrows$X1`  
 n <- length(ipsaggrpm)  
 plot((1:n - 1)/(n - 1), sort(ipsaggrpm), type="l",  
 main = "Quantiles for requests per minute",  
 xlab = "Quantile fraction",  
 ylab = "Requests per minute")  


As you can see in the quantiles table and the graph, there is a small percentage of 'abusive' source IPs. 

Web server request limit configuration
Fron Nginx documentation: " If the requests rate exceeds the rate configured for a zone, their processing is delayed such that requests are processed at a defined rate. Excessive requests are delayed until their number exceeds the maximum burst size in which case the request is terminated with an error 503 (Service Temporarily Unavailable). By default, the maximum burst size is equal to zero. ". In this sample case, considering that 99.6% of the IPs issued 63 requests per minute or less, considering each source IP, we can set the configuration as follows:

  limit_req_zone $binary_remote_addr zone=one:10m rate=63r/m;  
  ...   
  limit_req zone=one burst=21;  

And burst is set to one-third of 63 requests per minute. This is considering one web server. If a Load Balancer is forwarding requests evenly to two servers, consider half of the values above:


  limit_req_zone $binary_remote_addr zone=one:10m rate=32r/m;  
  ...   
  limit_req zone=one burst=11;  

Useful links
Matrix Operations in R
Dates and Times in R
Dates and Times Made Easy with lubridate - I didn't have to use lubridate, but I "almost" did.
Quantiles

Download the Jupyter notebook with the R script
You can download the Jupyter notebook with the R script here: https://1drv.ms/u/s!ArIEov4TZ9NM_HBIav7QiNR28Gmu

Saturday, April 26, 2014

Encrypting Emails with Mailvelope

In this video I show how to encrypt emails using Mailvelope. Mailvelope is an open-source project that works for Gmail, Yahoo Mail, Outlook and other webmail services. This video shows a step-by-step example on how to encrypt and decrypt emails using Mailvelope.


Como Encriptar Emails (Gmail, Yahoo, Outlook, etc)

Aqui está um vídeo que explica como encriptar emails usando o Mailvelope. Mailvelope é um projeto de código-livre que funciona para Gmail, Yahoo Mail, Outlook e outros serviços de email. O vídeo mostra um exemplo de encriptação e desencriptação passo a passo.


Tuesday, October 30, 2012

iOS 6 Ajax bug

As you may have read, there are some bugs in iOS 6 related to Ajax. This post explains it well. I had problems getting the " $.ajaxSetup " option to work, even though I didn't spend much time diagnosing it. Same with " $.ajaxPrefilter " ( see this post )
  While it appears that in Apache you can set the "Pragma: no-cache" just for posts on the server side, in IIS the http headers apply to all methods.
  I opted for creating a specific HTTP module that sets headers to disable client caching for certain specific requests. Here is the C# code:


    using System;
    using System.Web;
    using System.Text.RegularExpressions;

    ///

    /// HttpModule responsible for setting headers to disable cache in case of posts from iPad+iOS6+Safari. This environment may cache Ajax posts. 
    ///
    public class HttpNoCacheModule : IHttpModule
    {
        // user agent regular expression to capture Safari on iPad iOS 6
        private Regex ios6Regex = new Regex(@"iPad.+OS 6.+Safari", RegexOptions.IgnoreCase);

        public void Dispose()
        {            
        }

        public void Init(HttpApplication app)
        {            
            app.EndRequest += new EventHandler(this.OnEndRequest);            
        }

        private void OnEndRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            if (ios6Regex.IsMatch(context.Request.UserAgent))
            {
                if ("POST".Equals(context.Request.HttpMethod))
                {
                    context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
                    context.Response.AddHeader("pragma", "no-cache");
                    context.Response.CacheControl = "no-cache";
                }
            }
        }
        
    }

 

Thursday, October 04, 2012

Powershell to generate Delicious graph of tags

Here is a Powershell script that generates a graph in GEXF format of my Delicious tags. GEXF is an XML representation of a graph that may be consumed by Sigma.js, a JavaScript library for rendering graphs.


Set-StrictMode -version 1
Function CreateNode($id, $label, $xmlDoc, $nodes, $rootNS, $ZeroAttributeValue, $OneAttributeValue, $comment)
{
      $node = $xmlDoc.CreateElement("node", $rootNS)
      $node.SetAttribute("id",$id)
      $node.SetAttribute("label",$label)
           
      if ($ZeroAttributeValue) { # occurrences
          $attvalues = $xmlDoc.CreateElement("attvalues", $rootNS)
          $attvalue = $xmlDoc.CreateElement("attvalue", $rootNS)
          $attvalue.SetAttribute("for","0")
          $attvalue.SetAttribute("value",$ZeroAttributeValue)
          $attvalue.AppendChild($xmlDoc.CreateComment('occurrences'))  
          $attvalues.AppendChild($attvalue)
          $node.AppendChild($attvalues)
      }
           
      if ($OneAttributeValue) { # URL
          $attvalues = $xmlDoc.CreateElement("attvalues", $rootNS)
          $attvalue = $xmlDoc.CreateElement("attvalue", $rootNS)
          $attvalue.SetAttribute("for","1")
          $attvalue.SetAttribute("value",$OneAttributeValue)
          $attvalue.AppendChild($xmlDoc.CreateComment('URL'))  
          $attvalues.AppendChild($attvalue)
          $node.AppendChild($attvalues)
      }
 
      $node.AppendChild($attvalues)
      $node.AppendChild($xmlDoc.CreateComment($comment))  
 
      $nodes.AppendChild($node)
}

Function CreateEdge($id, $source, $target, $xmlDoc, $edges, $rootNS)
{      
       $edge = $xmlDoc.CreateElement("edge", $rootNS)
       $edge.SetAttribute("id",$edgeid)
       $edge.SetAttribute("source",$source)
       $edge.SetAttribute("target",$target)
             
       $edges.AppendChild($edge)
}
Function CreateAttributes($xmlDoc, $graph, $rootNS)
{
    $attribs = $xmlDoc.CreateElement("attributes", $rootNS)
    $attribs.SetAttribute("class","node")
    $attrib = $xmlDoc.CreateElement("attribute", $rootNS)
    $attrib.SetAttribute("id","0")
    $attrib.SetAttribute("title","occurrences-node")
    $attrib.SetAttribute("type","integer")
    $attribs.AppendChild($attrib)
    $attrib = $xmlDoc.CreateElement("attribute", $rootNS)
    $attrib.SetAttribute("id","1")
    $attrib.SetAttribute("title","url")
    $attrib.SetAttribute("type","string")
    $attribs.AppendChild($attrib)
    $graph.AppendChild($attribs)
}
Function EqualString($str1, $str2) {
   return !$str1.CompareTo($str2)
}
Function WhiteListTag($tag) {
  $lang = !'português'.CompareTo($tag) -or !'español'.CompareTo($tag)
  $black = (EqualString 'CSN_' $tag) -or (EqualString 'CSN_source' $tag)  -or (EqualString 'CSN_f' $tag) -or (EqualString 'CSN_t_' $tag) -or (EqualString 'CSN_freepage' $tag)
  $hasPrefix = $tag.StartsWith('CSN_')
  return $lang -or ( !$black -and $hasPrefix )
}
[void][system.reflection.assembly]::LoadFrom("E:\install\JsonNetBin\Net35\Newtonsoft.Json.dll")
Add-Type -AssemblyName "System.Net"
$json = ""
Get-Content C:\Users\gustavo.frederico\Documents\jsonDeliciousGF.txt |foreach{$json += $_ + "`r`n"}
$rss = [Newtonsoft.Json.Linq.JObject]::Parse($json)
[xml]$xmlDoc = New-Object system.Xml.XmlDocument
$xmlDoc.LoadXml('')
[System.Xml.XmlNamespaceManager] $nsmgr = $xmlDoc.NameTable
$rootNS = $xmlDoc.DocumentElement.NamespaceURI
$meta = $xmlDoc.CreateElement("meta", $rootNS)
$meta.SetAttribute("lastmodifieddate","2012-09-20")
$xmlDoc.LastChild.AppendChild($meta)
$creator = $xmlDoc.CreateElement("creator", $rootNS)
$creator.AppendChild($xmlDoc.CreateTextNode('CSNombre script'));
$meta.AppendChild($creator)
$desc = $xmlDoc.CreateElement("description", $rootNS)
$desc.AppendChild($xmlDoc.CreateTextNode('gexf representation of Delicious entries'));
$meta.AppendChild($desc)
$graph = $xmlDoc.CreateElement("graph", $rootNS)
$graph.SetAttribute("defaultedgetype","directed")
$graph.SetAttribute("mode","static")
CreateAttributes $xmlDoc $graph $rootNS
$nodes = $xmlDoc.CreateElement("nodes", $rootNS)
$xmlDoc.LastChild.AppendChild($graph)
$csnProps = $rss.Properties() | where { WhiteListTag $_.Name.ToString() }
$existingLabelNodesTags = @{}
# key: tag name , value: tag node id
$existingLabelNodesURLs = @{}
#  key: URL , value: url node id.
$tagNodeId = 0
$edgeid = 0
foreach($prop in $csnProps) {
   $existingLabelNodesTags.Add($prop.Name.ToString(), $tagNodeId)
 
   CreateNode $tagNodeId $prop.Name.ToString() $xmlDoc $nodes $rootNS $prop.Value.ToString() $null 'tag/label node'
     
   $tagNodeId++
 
#   Write-Host $prop.Value.ToString()
}
$wc = New-Object System.Net.WebClient
$edges = $xmlDoc.CreateElement("edges", $rootNS)
$URLNodeId = $tagNodeId
foreach($prop2 in $csnProps) {
   $currentTag = $prop2.Name.ToString()
 
   $tagNodeIdForCurrentTag = $existingLabelNodesTags.Get_Item($currentTag)
 
   $rawJson = $wc.DownloadString("http://feeds.delicious.com/v2/json/gcsfred/" + $currentTag) # "?count=99"
 
   $rawJson = "{ array: " + $rawJson + " }"
   $references = [Newtonsoft.Json.Linq.JObject]::Parse($rawJson)
 
   $referencesProps = $references.Properties()
     
   foreach($refProp in $references['array']) {  
     
       $uVal = $refProp['u'].ToString()
       $dVal = $refProp['d'].ToString()
       # create node for u  , d if it doesn't exist
     
       if ( $existingLabelNodesURLs.ContainsValue($uVal) ) {      
          $nodeIdURL = $existingLabelNodesURLs.Get_Item($uVal)
       } else {
          $existingLabelNodesURLs.Set_Item($uVal, $URLNodeId)
          $nodeIdURL = $URLNodeId        
         
          # create node for u and d
         
          CreateNode $nodeIdURL $dVal $xmlDoc $nodes $rootNS $null $uVal 'URL node'
         
          $URLNodeId++
       }
     
       # now, create edge between currentTag node and URL nodes
       
       CreateEdge $edgeid $tagNodeIdForCurrentTag $nodeIdURL $xmlDoc $edges $rootNS
       $edgeid++
     
       # end of u and d
     
       $otherTags = $refProp['t']
     
       $i=0
       while( $i -lt $otherTags.Count) {
         $otherTag = $otherTags[$i].ToString()
       
         $isWhite = WhiteListTag $otherTag
               
         if (!$isWhite) {
            $i++  
            continue
         }
       
         # fetch node id of $otherTag
     
         $nodeIdOtherTag = $existingLabelNodesTags.Get_Item($otherTag)
             
         CreateEdge $edgeid $nodeIdURL $nodeIdOtherTag $xmlDoc $edges $rootNS      
         $edgeid++
         $i++        
       }      
             
   }
   # end for each reference tag
}
$graph.AppendChild($nodes)
$graph.AppendChild($edges)
$xmlDoc.Save("c:\mytemp\foo.xml")


Saturday, September 29, 2012

Json.NET + Powershell - short example


( Json.NET from here. )

Example:

[void][system.reflection.assembly]::LoadFrom("E:\install\JsonNetBin\Net35\Newtonsoft.Json.dll")

$json = "{'channel': { 'title': 'James Newton-King' }}"

$rss = [Newtonsoft.Json.Linq.JObject]::Parse($json)

$rssTitle = $rss['channel']['title']

Write-Host $rssTitle.ToString()

-----

Output: James Newton-King

Wednesday, July 11, 2012

Domain, subdomain and deleting cookies

I was trying to delete with server-side code (C#) a mix of cookies types: some had the domain as the sub-domain, others had just the base domain without the sub-domain prefix. I wasn't able to delete the cookies of the base domain, since the running domain was the sub-domain. (Also, in C#, the cookie domain was null, so I had to use the request domain)
  This is what I did: I set pairs of expired cookies:



Func getBaseDomain = (someDomain) =>  System.Text.RegularExpressions.Regex.Replace(someDomain, @"(.+)((:?\..+){2,4})", "$2");

                    var responseCookie = new HttpCookie(cookieName);
                    responseCookie.Domain = Request.Url.Host;                  
                    responseCookie.Expires = DateTime.Now.AddYears(-1);
                    this.Response.Cookies.Add(responseCookie);

                    // try also to issue an expired cookie - to 'remove it'
                    // like - the one before but
                    // with the base domain. That is, without the sub-domain prefix.
                    // The cookie can only be deleted
                    // if the domain in the response matches exactly that
                    // assumed by the browser.

                    var responseCookie2 = new HttpCookie(cookieName);
                    responseCookie2.Domain = getBaseDomain(Request.Url.Host);
                    responseCookie2.Expires = DateTime.Now.AddYears(-1);
                    this.Response.Cookies.Add(responseCookie2);


 

Thursday, October 01, 2009

How to use the DnsQuery function (Windows Server 2008 R2)

There's a nice article on Microsoft's Support web site titled "How to use the DnsQuery function to resolve host names and host addresses with Visual C++ .NET" ( here ). The problem is that the signature of the DnsQuery Function has changed. The first argument takes a PCTSTR instead of a char * type.
I decided to rewrite the application using PCTSTR. Here's the new version:


#include //winsock
#include //DNS api's
#include //standard i/o
#include

//Usage of the program
void Usage(char *progname) {
fprintf(stderr,"Usage\n%s -n [HostName|IP Address] -t [Type] -s [DnsServerIp]\n",progname);
fprintf(stderr,"Where:\n\t\"HostName|IP Address\" is the name or IP address of the computer ");
fprintf(stderr,"of the record set being queried\n");
fprintf(stderr,"\t\"Type\" is the type of record set to be queried A or PTR\n");
fprintf(stderr,"\t\"DnsServerIp\"is the IP address of DNS server (in dotted decimal notation) ");
fprintf(stderr,"to which the query should be sent\n");
exit(1);
}


char* WChar2char(wchar_t * src, char * ppDest) {
size_t convertedChars = 0;
size_t origsize = wcslen(src) + 1;
wcstombs_s(&convertedChars, ppDest, origsize, src, _TRUNCATE);
return (char*)ppDest;
}

void Char2Wchar(char* src, wchar_t * ppDest) {
size_t convertedChars = 0;
size_t origsize = strlen(src) + 1;
mbstowcs_s(&convertedChars, ppDest, origsize, src, _TRUNCATE);
}

void ReverseIP(wchar_t* pIP)
{
wchar_t seps[1]; seps[0] = (wchar_t)'.';
wchar_t *token;
char pIPSec[4][4];
int i=0;
token = wcstok( pIP, seps);
while( token != NULL )
{
/* While there are "." characters in "string" */
sprintf(pIPSec[i],"%s", token);
/* Get next "." character: */
token = wcstok( NULL, seps );
i++;
}
swprintf(pIP,L"%s.%s.%s.%s.%s", pIPSec[3],pIPSec[2],pIPSec[1],pIPSec[0],"IN-ADDR.ARPA");
}


// the main function
void __cdecl main(int argc, char *argv[])
{
DNS_STATUS status; //Return value of DnsQuery_A() function.
PDNS_RECORD pDnsRecord; //Pointer to DNS_RECORD structure.
PIP4_ARRAY pSrvList = NULL; //Pointer to IP4_ARRAY structure.
WORD wType; //Type of the record to be queried.
wchar_t pOwnerName[255]; //Owner name to be queried.
wchar_t pReversedIP[255]; //Reversed IP address.
wchar_t DnsServIp[255]; //DNS server ip address.
DNS_FREE_TYPE freetype ;
freetype = DnsFreeRecordListDeep;
IN_ADDR ipaddr;
char * chOwnerName = NULL;

if(argc > 4) {
for(int i = 1; i < argc ; i++) {
if ( (argv[i][0] == '-') || (argv[i][0] == '/') ) {
switch(tolower(argv[i][1])) {
case 'n':
//pOwnername=
chOwnerName = argv[++i];
Char2Wchar(chOwnerName, (wchar_t *) pOwnerName);
break;
case 't':
if (!stricmp(argv[i+1], "A") )
wType = DNS_TYPE_A; //Query host records to resolve a name.
else if (!stricmp(argv[i+1], "PTR") )
{
//pOwnerName should be in "xxx.xxx.xxx.xxx" format
if(wcslen(pOwnerName)<=15)
{
//You must reverse the IP address to request a Reverse Lookup
//of a host name.
swprintf(pReversedIP,L"%s",pOwnerName);
ReverseIP(pReversedIP);
size_t sizeRev = wcslen(pReversedIP) + 1;
wcsncpy_s(pOwnerName, sizeRev, pOwnerName, 255);
//pOwnerName=pReversedIP;
wType = DNS_TYPE_PTR; //Query PTR records to resolve an IP address
}
else
{
Usage(argv[0]);
}
}
else
Usage(argv[0]);
i++;
break;

case 's':
// Allocate memory for IP4_ARRAY structure.
pSrvList = (PIP4_ARRAY) LocalAlloc(LPTR,sizeof(IP4_ARRAY));
if(!pSrvList){
printf("Memory allocation failed \n");
exit(1);
}
if(argv[++i]) {
wchar_t aarray[300];
Char2Wchar(argv[i], (wchar_t*)aarray);
wcscpy(DnsServIp,aarray);
char tmp[500];

pSrvList->AddrCount = 1;
pSrvList->AddrArray[0] = inet_addr(WChar2char(DnsServIp, (char*) tmp)); //DNS server IP address
break;
}

default:
Usage(argv[0]);
break;
}
}
else
Usage(argv[0]);
}
}
else
Usage(argv[0]);

// Calling function DnsQuery to query Host or PTR records
status = DnsQuery(pOwnerName, //Pointer to OwnerName.
wType, //Type of the record to be queried.
DNS_QUERY_BYPASS_CACHE, // Bypasses the resolver cache on the lookup.
pSrvList, //Contains DNS server IP address.
&pDnsRecord, //Resource record that contains the response.
NULL); //Reserved for future use.

if (status){
if(wType == DNS_TYPE_A)
printf("Failed to query the host record for %s and the error is %d \n", pOwnerName, status);
else
printf("Failed to query the PTR record and the error is %d \n", status);
} else {
if(wType == DNS_TYPE_A) {
//convert the Internet network address into a string
//in Internet standard dotted format.
ipaddr.S_un.S_addr = (pDnsRecord->Data.A.IpAddress);
printf("The IP address of the host %s is %s \n", pOwnerName,inet_ntoa(ipaddr));

// Free memory allocated for DNS records.
DnsRecordListFree(pDnsRecord, freetype);
}
else {
printf("The host name is %s \n",(pDnsRecord->Data.PTR.pNameHost));

// Free memory allocated for DNS records.
DnsRecordListFree(pDnsRecord, freetype);
}
}
LocalFree(pSrvList);
}




(keywords: DNS, DnsQuery, Windows Server 2008 R2 )

Wednesday, May 13, 2009

Real World Software Engineering – XXIII

On the intuition of risk of throw-away code

At times, while I'm creating software, I have a feeling (and it is just a feeling) that what I'm creating is going to the garbage. Being subjective, there is no way at the time to be sure if the preoccupation is warranted or not. I think that this feeling arises out of different factors:

- knowing that you are coding something for a requirement that brings little value to the end user. Apparently the business thought about the requirement, but you are not convinced.
- knowing that you are coding something for a requirement that is not firm in consensus.
- knowing that you are coding something for a requirement that is not well understood by the client. (The client may 'know' the requirement but not 'master' it. When that happens there's risk in the requirement.)
- knowing that you are introducing dependencies into other software that were not debated in the group/project.
- knowing that you are coding something for a requirement which is not explicit, that you think you are going to need but you are not so sure. That is, if you do not code it, there's the risk that there will be a 'bug' created saying that your feature is not complete.

Tuesday, January 13, 2009

Music and software

I shake my head when I hear about software that manipulate sound instead of symbolic representations of music. It is like hearing about a compiler that processes the voice of the programmer instead of source code in text format.

Friday, October 31, 2008

Escaping XML

I had a requirement to escape XML. It was easier for me to work with escaped XML and encode it at the end only. I couldn't find an 'ubiquitous' tool to encode it. So I did the minimum to get it done.

Ingredient: XML text, unencoded

Step 1: Get sed (sed for Windows, in my case)

Step 2: In a prompt, type



Problem solved.
Note: (To really escape XML, you also need to escape quotes (") and ampersand (&). That is left as an exercise to the reader )

Thursday, October 09, 2008

My Daily SharePoint Frustration - VIII

Note to self:
When importing the list template from a manifest.xml inside a .stp file generated by SharePoint Designer into a schema.xml, remember to do these steps:
After copying the List element from the manifest to the schema,

- For each View element inside schema.xml:
- Create BaseViewID elements, if these don't exist. Sort the View[BaseViewID] elements and attributes ascending.
- Remove the folder content type.
- Add a SetupPath attribute to each View (SetupPath="pages\viewpage.aspx" worked)
- Add a ContentTypeID attribute. Just the prefix ('parent' of the content type declared in schema.xml) of the hex ID seems to work.

If I don't follow these steps, I get weird errors, such as "File Not Found" or those HREF COM errors. Even though the list does get instantiated.

Tuesday, September 23, 2008

My daily SharePoint frustration - V

The error message:

Could not load type 'Blah.Control'. at System.Web.UI.TemplateParser.GetType(String typeName, Boolean ignoreCase, Boolean throwOnError)

at System.Web.UI.TemplateParser.ProcessInheritsAttribute(String baseTypeName, String codeFileBaseTypeName, String src, Assembly assembly)
at System.Web.UI.TemplateParser.PostProcessMainDirectiveAttributes(IDictionary parseData)

The context:
I was trying to create a custom field type with a custom control. The problem appeared when I was trying to create a new column in a list. Apparently SharePoint wasn't finding the class for the inheritance of the control, declared in the ascx file.

The solution (or workaround):
I checked that the public key token was the same in the GAC, in the ascx.
I checked that the FieldTypeClass in the field declaration was correct in my fldtypes_blahblahblah.xml
I reset IIS. (I think this is what "solved" the problem). It is as if resetting IIS forced SharePoint to "reconsider" the proper version of the assembly in the GAC (flushed whatever from memory).

Wednesday, September 03, 2008

Passing parameters between pages via URL in SharePoint

Many blog posts write about how to pre-populate fields in SharePoint using JavaScript. It wasn't clear to me how to pass parameters through the URL between SharePoint pages.

For instance, assume that a certain page has a URL parameter passed to it. How to embed this value in the URL to a new list item? (Often a customized version of NewForm.aspx)

Consider two pages: the source page and the NewCustomForm.aspx. This is how I did it:

1. I copied an existing schema.xml from a 'base' list template. In my case, 12\TEMPLATE\FEATURES\DiscussionsList\Discuss\schema.xml.

2. Adding a new field to the content type, I added a new FieldRef under ContentTypes/FieldRefs in schema.xml.

<viewfields>
<!-- custom by Gustavo -->
<fieldref name="MyField">
</fieldref>
<!-- end custom by Gustavo -->

3. I also added a new Field under Fields.
<Field
Type="Text"
Name="MyField"
DisplayName="MyField">
</Field>

4. I added a reference to the new field in ViewFields under the proper View.

<!-- custom by Gustavo -->
<FieldRef Name="MyField">
</FieldRef>
<!-- end custom by Gustavo -->

5. Now the brain surgery: the task now is to embed the URL parameter in the link that creates a new item. In my case, the view I was working in (the view defined in schema.xml) defined a hyperlink with text and the URL in it. My task was to modify this hyperlink to append the parameter. (careful: if the redirection between pages is done by JavaScript, the href in the a element may not be the actual URL. This was my case). I begin adding a JavaScript function under the View/Toolbar element. It just gets the value of the parameter:

<Toolbar Position="After" Type="Freeform">
<IfHasRights>
<RightsChoices>
<RightsGroup PermAddListItems="required" />
</RightsChoices>
<Then>
<!-- customization by Gustavo -->
<HTML>
<![CDATA[<SCRIPT>

function GetMyFieldFromURL() {

var qs = location.search.substring(1, location.search.length);

var args = qs.split("&");

var vals = new Object();

for (var i=0; i < args.length; i++) {

var nameVal = args[i].split("=");

var temp = unescape(nameVal[1]).split('+');

nameVal[1] = temp.join(' ');

vals[nameVal[0]] = nameVal[1];
if("MyField" == nameVal[0]) {
return nameVal[1];
}
}

return "";

}
</SCRIPT>]]>
</HTML>
<!-- end of this customization by Gustavo -->

6. Now I can append the parameter to the URL:

<HTML><![CDATA[ <table width=100% cellpadding=0 cellspacing=0 border=0 > <tr> <td colspan="2" class="ms-partline"><IMG SRC="/_layouts/images/blank.gif" width=1 height=1 alt=""></td> </tr> <tr> <td class="ms-addnew" style="padding-bottom: 3px"> <img src="/_layouts/images/rect.gif" alt=""> <a class="ms-addnew" ID="idAddNewDiscuss" href="]]></HTML>
<URL Cmd="New" />
<HTML><![CDATA[" ONCLICK="javascript:NewItem(']]></HTML>
<URL Cmd="New" />
<HTML><![CDATA[?MyField='+GetMyFieldFromURL(), true);javascript:return false;" target="_self">]]></HTML>
<HTML>
<!-- _locID_text="onetid6" _locComment="{StringCategory=HTX}" -->$Resources:core,Add_New_Discussion;
</HTML>
...

7. Continuing the brain surgery: include JavaScript under the PlaceHolderMain section that does the lookup and populates the fields in the target form (customized NewForm.aspx). I followed this blog post:
http://blogs.msdn.com/sharepointdesigner/archive/2007/06/13/using-javascript-to-manipulate-a-list-form-field.aspx

I modified copied this function to populate a regular text field instead of select options.

function fillDefaultValues() {
...

setTextFromFieldName("MyField", vals["MyField"]);

}

function setTextFromFieldName(fieldName, value) {
if (value == undefined) return;
var theInput = getTagFromIdentifierAndTitle("input","",fieldName);
theInput.value=value;
}

And this is it. Just wait for the patient to wake up from the anesthesia and everything will be alright.

This is how to see it in action:
Put your list somewhere.
Verify that the hyperlink to create a new item actually has the parameter (careful: if the redirection between pages is done by JavaScript, the href in the a element may not be the actual URL)
Click on the link to create the new list item. The parameter must be in the URL at this stage.
Note that the value of the URL made its way to the form.


If you find an easier way to do this, please let me know.

Buzzword of the day: metadata

The buzzword of the day is "metadata". The term is often abused by software developers and architects, frequently ending up in documentation for the end user. What a beautiful idea to annotate data, or set data about data. It is more interesting when the data is easily accessible and useful.

Tuesday, September 02, 2008

Software design

Part of the task of software design in my experience consists of describing the system with some language that is understandable to developers (and tester and often stakeholders). It is a modeling exercise. And I emphasize language.

Wednesday, August 20, 2008

GAC it within Visual Studio



It is convenient to GAC a .NET assembly directly in Visual Studio 2008. In Visual Studio, go to "Tools" in the menu and choose "External Tools..." Enter the information in the dialog as shown above. (The assembly in your project may need to have a strong name). After building your project, select it and choose "GAC it" from "Tools". There you go, you have just GACed your assembly.

Tuesday, August 12, 2008

Hidden pockets in SharePoint's property bags

Yes, there are hidden pockets in SharePoint's property bags. Even if you call web.Properties.Update() you may not persist the changes. Call web.Update() after. For instance, this is how one can delete from the property bag:

if (currentWeb.Properties.ContainsKey(newKey))
{
currentWeb.AllowUnsafeUpdates = true;

currentWeb.Properties.Remove(newKey);
currentWeb.Properties.Update();

currentWeb.AllProperties.Remove(newKey);
currentWeb.AllowUnsafeUpdates = false;

currentWeb.AllowUnsafeUpdates = true;
currentWeb.Update();
currentWeb.AllowUnsafeUpdates = false;
}

Yes, one must wrap changes with the AllowUnsafeUpdates set.
(I spent hours trying to understand this hidden feature)

Monday, July 28, 2008

Zune fighting with video driver

This is the result of the Zune software fighting with my ATI video driver on my DELL notebook. What I think is happening is that there's some bug in the video driver, and Zune is using some custom rendering. It happens intermittently. Who's to blame? The ATI video driver, I suppose.